Поиск…


замечания

Чтобы запустить любой из этих примеров, просто назовите их так:

static void Main()
{
    new Program().ProcessDataAsync();
    Console.ReadLine();
}

ASP.NET Configure Await

Когда ASP.NET обрабатывает запрос, поток создается из пула потоков и создается контекст запроса . Контекст запроса содержит информацию о текущем запросе, к которому можно получить доступ через статическое свойство HttpContext.Current . Затем контекст запроса для запроса присваивается потоку, обрабатывающему запрос.

Конкретный контекст запроса может быть активен только по одному потоку за раз .

Когда выполнение достигает await , поток, обрабатывающий запрос, возвращается в пул потоков, пока выполняется асинхронный метод, и контекст запроса свободен для использования другого потока.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    var products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    // Execution continues using the original request context.
    return View(products);
}

Когда задача завершается, пул потоков назначает другой поток для продолжения выполнения запроса. Затем контекст запроса присваивается этому потоку. Это может быть или не быть исходной нитью.

блокировка

Когда результат вызова метода async синхронно, могут возникнуть взаимоблокировки. Например, следующий код приведет к взаимоблокировке при IndexSync() :

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Это связано с тем, что по умолчанию ожидаемая задача, в этом случае db.Products.ToListAsync() захватит контекст (в случае ASP.NET контекст запроса) и попытается использовать его после его завершения.

Когда весь стек вызовов асинхронен, проблем нет, потому что, как только await достигнуто, исходный поток освобождается, освобождая контекст запроса.

Когда мы блокируем синхронное использование Task.Result или Task.Wait() (или других методов блокировки), исходный поток все еще активен и сохраняет контекст запроса. Ожидаемый метод все еще работает асинхронно, и как только обратный вызов пытается выполнить, т. Е. Как только ожидаемая задача вернулась, он пытается получить контекст запроса.

Поэтому тупик возникает из-за того, что, пока блокирующий поток с контекстом запроса ожидает завершения асинхронной операции, асинхронная операция пытается получить контекст запроса для завершения.

ConfigureAwait

По умолчанию вызовы ожидаемой задачи будут захватывать текущий контекст и пытаться возобновить выполнение в контексте после завершения.

Используя ConfigureAwait(false) это может быть предотвращено, а взаимоблокировки можно избежать.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);

    // Execution resumes on a "random" thread from the pool without the original request context
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Это позволяет избежать блокировок, когда необходимо блокировать асинхронный код, однако это происходит за счет потери контекста в продолжении (код после ожидания вызова).

В ASP.NET это означает, что если ваш код после вызова await someTask.ConfigureAwait(false); пытается получить доступ к информации из контекста, например HttpContext.Current.User тогда информация была потеряна. В этом случае HttpContext.Current имеет значение NULL. Например:

public async Task<ActionResult> Index()
{
    // Contains information about the user sending the request
    var user = System.Web.HttpContext.Current.User;

    using (var client = new HttpClient())
    {
        await client.GetAsync("http://google.com").ConfigureAwait(false);
    }

    // Null Reference Exception, Current is null
    var user2 = System.Web.HttpContext.Current.User;

    return View();
}

Если ConfigureAwait(true) используется (эквивалентно отсутствию ConfigureAwait), то как user и user2 заполняются теми же данными.

По этой причине часто рекомендуется использовать ConfigureAwait(false) в библиотечном коде, где контекст больше не используется.

Асинхронный / Await

Ниже приведен простой пример того, как использовать async / await для выполнения некоторых интенсивных операций времени в фоновом режиме, сохраняя при этом возможность делать некоторые другие вещи, которые не нужно ждать от времени, необходимого для завершения.

Однако, если вам нужно позже работать с результатом интенсивного времени, вы можете сделать это, ожидая выполнения.

public async Task ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    // Wait for TimeintensiveMethod to complete and get its result
    int x = await task;
    Console.WriteLine("Count: " + x);
}

private async Task<int> TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = await reader.ReadToEndAsync();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

BackgroundWorker

Ниже приведен простой пример использования объекта BackgroundWorker для выполнения интенсивных операций в фоновом потоке.

Вам нужно:

  1. Определите рабочий метод, который выполняет трудоемкую работу и вызывается из обработчика события для DoWork события BackgroundWorker .
  2. Запустите выполнение с помощью RunWorkerAsync . Любой аргумент требуется по методе работника , прикрепленной к DoWork может быть передан в через DoWorkEventArgs параметр RunWorkerAsync .

В дополнение к событию DoWork класс BackgroundWorker также определяет два события, которые должны использоваться для взаимодействия с пользовательским интерфейсом. Это необязательно.

  • Событие RunWorkerCompleted запускается, когда обработчики DoWork завершены.
  • Событие ProgressChanged запускается при ReportProgress метода ReportProgress .
public void ProcessDataAsync()
{
    // Start the time intensive method
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += BwDoWork;
    bw.RunWorkerCompleted += BwRunWorkerCompleted;
    bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // we can access possible return values of our Method via the Parameter e
    Console.WriteLine("Count: " + e.Result);
}

// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
    e.Result = TimeintensiveMethod(e.Argument);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

       for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

задача

Ниже приведен простой пример того, как использовать Task для выполнения некоторого времени в фоновом процессе.

Все, что вам нужно сделать, это обернуть ваш метод интенсивного времени в Task.Run() .

public void ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    Console.WriteLine("Count: " + t.Result);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Нить

Ниже приведен простой пример того, как использовать Thread для выполнения некоторого времени в фоновом процессе.

public async void ProcessDataAsync()
{
    // Start the time intensive method
    Thread t = new Thread(TimeintensiveMethod);

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

private void TimeintensiveMethod()
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
    {
        string v = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            v.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");
}

Как вы можете видеть, мы не можем вернуть значение из нашего TimeIntensiveMethod потому что Thread ожидает, что в качестве параметра будет использоваться метод void.

Чтобы получить возвращаемое значение из Thread используйте либо событие, либо следующее:

int ret;
Thread t= new Thread(() => 
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something to demonstrate the coolness of await-async
    ret = new Random().Next(100);
});

t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);

Задание «запустить и забыть»

В некоторых случаях (например, ведение журнала) может оказаться полезным запустить задачу и не ждать результата. Следующее расширение позволяет запустить задачу и продолжить выполнение кода останова:

public static class TaskExtensions
{
    public static async void RunAndForget(
        this Task task, Action<Exception> onException = null)
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            onException?.Invoke(ex);
        }
    }
}

Результат ожидается только внутри метода расширения. Поскольку используется async / await , можно поймать исключение и вызвать необязательный метод его обработки.

Пример использования расширения:

var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
    e =>
    {
        // Something went wrong, handle it.
    });


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow