Szukaj…


Uwagi

Aby uruchomić dowolny z tych przykładów, po prostu wywołaj je w ten sposób:

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

ASP.NET Konfiguruj oczekiwanie

Gdy ASP.NET obsługuje żądanie, wątek jest przypisywany z puli wątków i tworzony jest kontekst żądania . Kontekst żądania zawiera informacje o bieżącym żądaniu, do którego można uzyskać dostęp za pośrednictwem statycznej właściwości HttpContext.Current . Kontekst żądania jest następnie przypisywany do wątku obsługującego żądanie.

Dany kontekst żądania może być aktywny tylko dla jednego wątku na raz .

Gdy wykonanie osiągnie await , wątek obsługujący żądanie jest zwracany do puli wątków, podczas gdy działa metoda asynchroniczna, a kontekst żądania jest dostępny dla innego wątku.

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);
}

Po zakończeniu zadania pula wątków przypisuje kolejny wątek, aby kontynuować wykonywanie żądania. Kontekst żądania jest następnie przypisywany do tego wątku. To może być lub nie oryginalny wątek.

Bloking

Podczas oczekiwania na wywołanie metody async mogą wystąpić synchroniczne zakleszczenia. Na przykład następujący kod spowoduje zakleszczenie po 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;       
}

Wynika to z tego, że domyślnie oczekiwane zadanie w tym przypadku db.Products.ToListAsync() przechwytuje kontekst (w przypadku ASP.NET kontekst żądania) i próbuje go użyć po zakończeniu.

Gdy cały stos wywołań jest asynchroniczny, nie ma problemu, ponieważ po osiągnięciu await oryginalny wątek zostaje zwolniony, uwalniając kontekst żądania.

Gdy Task.Result synchronicznie za pomocą Task.Result lub Task.Wait() (lub innych metod blokowania) oryginalny wątek jest nadal aktywny i zachowuje kontekst żądania. Oczekiwana metoda nadal działa asynchronicznie i po próbie uruchomienia wywołania zwrotnego, tj. Po zwróceniu oczekiwanego zadania, próbuje uzyskać kontekst żądania.

Dlatego pojawia się impas, ponieważ podczas gdy wątek blokujący z kontekstem żądania czeka na zakończenie operacji asynchronicznej, operacja asynchroniczna próbuje uzyskać kontekst żądania w celu jej zakończenia.

ConfigureAwait

Domyślnie wywołania oczekiwanego zadania przechwytują bieżący kontekst i po zakończeniu podejmą próbę wznowienia wykonania w tym kontekście.

Używając ConfigureAwait(false) można temu zapobiec i uniknąć zakleszczeń.

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;       
}

Pozwala to uniknąć zakleszczeń, gdy konieczne jest blokowanie kodu asynchronicznego, jednak wiąże się to z kosztem utraty kontekstu w kontynuacji (kod po wywołaniu, aby czekać).

W ASP.NET oznacza to, że jeśli twój kod po wywołaniu await someTask.ConfigureAwait(false); próbuje uzyskać dostęp do informacji z kontekstu, na przykład HttpContext.Current.User a następnie informacje zostały utracone. W tym przypadku HttpContext.Current ma wartość NULL. Na przykład:

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();
}

Jeśli zostanie użyta opcja ConfigureAwait(true) (odpowiednik braku opcji ConfigureAwait), zarówno user jak i user2 zostaną zapełnione tymi samymi danymi.

Z tego powodu często zaleca się użycie ConfigureAwait(false) w kodzie biblioteki, gdy kontekst nie jest już używany.

Asynchronizuj / czekaj

Poniżej znajduje się prosty przykład użycia funkcji asynchronicznej / oczekiwania na wykonanie pewnych pracochłonnych czynności w tle, przy jednoczesnym zachowaniu opcji wykonywania innych czynności, które nie muszą czekać na wykonanie pracochłonnych prac.

Jeśli jednak będziesz musiał później pracować z wynikiem metody czasochłonnej, możesz to zrobić, czekając na wykonanie.

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

Poniżej znajduje się prosty przykład użycia obiektu BackgroundWorker do wykonywania czasochłonnych operacji w wątku w tle.

Musisz:

  1. Zdefiniuj metodę roboczą, która wykonuje czasochłonną pracę, i wywołaj ją z modułu obsługi zdarzeń dla zdarzenia DoWork w BackgroundWorker .
  2. Rozpocznij wykonywanie za pomocą RunWorkerAsync . Dowolny argument wymagany przez metodę DoWorkEventArgs dołączoną do DoWork można przekazać za pomocą parametru RunWorkerAsync do RunWorkerAsync .

Oprócz zdarzenia DoWork klasa BackgroundWorker definiuje również dwa zdarzenia, które powinny być używane do interakcji z interfejsem użytkownika. Te są opcjonalne.

  • Zdarzenie RunWorkerCompleted jest wyzwalane po zakończeniu procedur obsługi DoWork .
  • Zdarzenie ProgressChanged jest wyzwalane, gdy ReportProgress jest metoda 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);
}

Zadanie

Poniżej znajduje się prosty przykład użycia Task do wykonywania pracochłonnych czynności w tle.

Wszystko, co musisz zrobić, to owinąć swoją czasochłonną metodę w 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);
}

Wątek

Poniżej znajduje się prosty przykład użycia Thread do wykonywania pracochłonnych czynności w tle.

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.");
}

Jak widać, nie możemy zwrócić wartości z naszego TimeIntensiveMethod ponieważ Thread oczekuje parametru void jako parametru.

Aby uzyskać wartość zwrotną z Thread użyj zdarzenia lub następujących czynności:

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);

Zadanie „uruchom i zapomnij” rozszerzenie

W niektórych przypadkach (np. Rejestrowanie) przydatne może być uruchomienie zadania i nie czekanie na wynik. Następujące rozszerzenie pozwala uruchomić zadanie i kontynuować wykonywanie kodu resztowego:

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);
        }
    }
}

Oczekiwany jest wynik tylko w metodzie rozszerzenia. Ponieważ używane jest async / await , możliwe jest wychwycenie wyjątku i wywołanie opcjonalnej metody jego obsługi.

Przykład użycia rozszerzenia:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow