it-swarm.com.de

Aufrufen von asynchronen Methoden aus nicht asynchronem Code

Ich aktualisiere gerade eine Bibliothek mit einer API-Oberfläche, die in .NET 3.5 erstellt wurde. Folglich sind alle Methoden synchron. Ich kann die API nicht ändern (d. H. Rückgabewerte in Task konvertieren), da hierfür alle Aufrufer geändert werden müssten. Also bleibt mir die Möglichkeit, asynchrone Methoden am besten synchron aufzurufen. Dies erfolgt im Kontext von ASP.NET 4, ASP.NET Core und .NET/.NET Core-Konsolenanwendungen.

Möglicherweise war mir nicht klar genug - die Situation ist, dass ich vorhandenen Code habe, der nicht asynchron ist, und ich möchte neue Bibliotheken wie System.Net.Http und das AWS SDK verwenden, die nur asynchrone Methoden unterstützen. Ich muss also die Lücke schließen und in der Lage sein, Code zu haben, der synchron aufgerufen werden kann, aber dann an anderer Stelle asynchrone Methoden aufrufen kann.

Ich habe viel gelesen und es gibt einige Male, bei denen dies gefragt und beantwortet wurde.

Aufruf einer asynchronen Methode von einer nicht asynchronen Methode

Wartet synchron auf eine asynchrone Operation und warum friert Wait () das Programm hier ein

Aufrufen einer asynchronen Methode von einer synchronen Methode

Wie würde ich eine asynchrone Task <T> -Methode synchron ausführen?

Asynchrone Methode synchron aufrufen

Wie rufe ich eine asynchrone Methode von einer synchronen Methode in C # auf?

Das Problem ist, dass die meisten Antworten unterschiedlich sind! Der häufigste Ansatz, den ich gesehen habe, ist die Verwendung von .Result. Dies kann jedoch zu einem Deadlock führen. Ich habe die folgenden Methoden ausprobiert und sie funktionieren, aber ich bin mir nicht sicher, welche Methode am besten geeignet ist, um Deadlocks zu vermeiden, eine gute Leistung zu erzielen und die Laufzeit zu optimieren (in Bezug auf das Einhalten von Aufgabenplanern, Aufgabenerstellungsoptionen usw.) ). Gibt es eine endgültige Antwort? Was ist der beste Ansatz?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }
58
Erick T

Also bleibt mir die Möglichkeit, asynchrone Methoden am besten synchron aufzurufen.

Erstens ist das in Ordnung. Ich sage das, weil es bei Stack Overflow üblich ist, dies als eine Tat des Teufels als pauschale Aussage ohne Rücksicht auf den konkreten Fall herauszustellen.

Es ist nicht erforderlich, vollständig asynchron zu sein aus Gründen der Korrektheit. Das Blockieren von asynchronen Daten, um die Synchronisierung zu ermöglichen, hat Leistungskosten zur Folge, die von Bedeutung oder völlig irrelevant sein können. Es kommt auf den konkreten Fall an.

Deadlocks stammen von zwei Threads, die gleichzeitig versuchen, in denselben Single-Thread-Synchronisationskontext zu gelangen. Jede Technik, die dies vermeidet, vermeidet zuverlässig Deadlocks, die durch Blockieren verursacht werden.

Hier sind alle Ihre Aufrufe von .ConfigureAwait(false) sinnlos, weil Sie nicht warten.

Die Verwendung von RunSynchronously ist ungültig, da nicht alle Aufgaben auf diese Weise verarbeitet werden können.

.GetAwaiter().GetResult() unterscheidet sich von Result/Wait() darin, dass es das Ausbreitungsverhalten von await Ausnahmen nachahmt. Sie müssen sich entscheiden, ob Sie das wollen oder nicht. (Erforschen Sie also, was dieses Verhalten ist. Sie müssen es hier nicht wiederholen.)

Außerdem weisen alle diese Ansätze eine ähnliche Leistung auf. Sie ordnen ein OS-Ereignis auf die eine oder andere Weise zu und blockieren es. Das ist der teure Teil. Ich weiß nicht, welcher Ansatz der absolut günstigste ist.

Ich persönlich mag das Muster Task.Run(() => DoSomethingAsync()).Wait();, weil es Deadlocks kategorisch vermeidet, einfach ist und einige Ausnahmen nicht verbirgt, die GetResult() möglicherweise verbirgt. Sie können jedoch auch GetResult() verwenden.

72
usr

Ich aktualisiere gerade eine Bibliothek mit einer API-Oberfläche, die in .NET 3.5 erstellt wurde. Folglich sind alle Methoden synchron. Ich kann die API nicht ändern (d. H. Rückgabewerte in Task konvertieren), da hierfür alle Aufrufer geändert werden müssten. Also bleibt mir die Möglichkeit, asynchrone Methoden am besten synchron aufzurufen.

Es gibt keine universelle "beste" Möglichkeit, das Synchronisations-Über-Asynchronisations-Anti-Muster auszuführen. Nur eine Vielzahl von Hacks, die jeweils ihre eigenen Nachteile haben.

Was ich empfehle ist, dass Sie die alten synchronen APIs behalten und dann asynchrone APIs daneben einführen. Sie können dies mit dem "boolean argument hack", wie in meinem MSDN-Artikel über Brownfield Async beschrieben tun.

Zunächst eine kurze Erläuterung der Probleme bei jedem Ansatz in Ihrem Beispiel:

  1. ConfigureAwait macht nur Sinn, wenn es ein await gibt; sonst macht es nichts.
  2. Result fügt Ausnahmen in ein AggregateException ein; Wenn Sie blockieren müssen, verwenden Sie stattdessen GetAwaiter().GetResult().
  3. Task.Run Führt seinen Code auf einem Threadpool-Thread aus (offensichtlich). Dies ist in Ordnung nur, wenn der Code kann auf einem Thread-Pool-Thread ausgeführt wird.
  4. RunSynchronously ist eine erweiterte API, die in äußerst seltenen Situationen bei der Ausführung dynamischer aufgabenbasierter Parallelität verwendet wird. Du bist überhaupt nicht in diesem Szenario.
  5. Task.WaitAll Mit einer einzelnen Aufgabe ist dasselbe wie nur Wait().
  6. async () => await x ist nur eine weniger effiziente Art, () => x zu sagen.
  7. Das Blockieren einer vom aktuellen Thread gestarteten Aufgabe kann zu Deadlocks führen .

Hier ist die Aufteilung:

// Problems (1), (3), (6)
result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (3)
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (7)
result = task().ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (2), (3)
result = Task.Run(task).Result;

// Problems (3)
result = Task.Run(task).GetAwaiter().GetResult();

// Problems (2), (4)
var t = task();
t.RunSynchronously();
result = t.Result;

// Problems (2), (5)
var t1 = task();
Task.WaitAll(t1);
result = t1.Result;

Anstelle eines dieser Ansätze sollten Sie vorhandenen, funktionierenden synchronen Code zusammen mit dem neueren natürlich asynchronen Code verwenden. Wenn in Ihrem vorhandenen Code beispielsweise WebClient verwendet wurde:

public string Get()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

und Sie möchten eine asynchrone API hinzufügen, dann würde ich es so machen:

private async Task<string> GetCoreAsync(bool sync)
{
  using (var client = new WebClient())
  {
    return sync ?
        client.DownloadString(...) :
        await client.DownloadStringTaskAsync(...);
  }
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

oder, wenn Sie muss aus irgendeinem Grund HttpClient verwenden:

private string GetCoreSync()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

private static HttpClient HttpClient { get; } = ...;

private async Task<string> GetCoreAsync(bool sync)
{
  return sync ?
      GetCoreSync() :
      await HttpClient.GetString(...);
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

Mit diesem Ansatz würde Ihre Logik in die Core Methoden gehen, die synchron oder asynchron ausgeführt werden können (wie durch den sync Parameter bestimmt). Wenn synctrue ist, geben die Kernmethoden muss eine bereits abgeschlossene Aufgabe zurück. Verwenden Sie zur Implementierung synchrone APIs für die synchrone Ausführung und asynchrone APIs für die asynchrone Ausführung.

Schließlich empfehle ich, die synchronen APIs zu verwerfen.

31
Stephen Cleary