Dieser Artikel wurde maschinell übersetzt.

Asynchrone Programmierung

Muster für asynchrone MVVM-Anwendungen: Services

Stephen Cleary

Dies ist der dritte Artikel in einer Reihe auf der Kombination von Async und erwarten mit dem Model-View-ViewModel (MVVM) Muster. Im ersten Artikel entwickelte ich eine Methode für die Datenbindung für eine asynchrone Operation. Im zweiten Fall hielt ich ein paar mögliche Implementierungen von einer asynchronen ICommand. Nun, werde ich die Dienstschicht und asynchrone Adressdienste zuwenden.

Ich werde nicht mit einer Benutzeroberfläche handeln. In der Tat nicht die Muster in diesem Artikel speziell für MVVM; Sie betreffen gleichermaßen gut für jede Art von Anwendung. Die asynchrone Datenbindung und Befehl-Muster, die in meiner früheren Artikeln erforscht sind ganz neu; der asynchrone Dienst-Muster in diesem Artikel werden mehr hergestellt. Dennoch sind auch etablierte Muster nur Muster.

Asynchrone Schnittstellen

"Programm in eine Schnittstelle, keine Implementierung." Als dieses Zitat aus "Design Patterns: Elemente wiederverwendbarer objektorientierter Software"(Addison-Wesley, 1994, p. 18) andeutet, Schnittstellen sind eine wichtige Komponente der richtige objektorientierten Entwurf. Sie erlauben Ihren Code um eine Abstraktion statt einen konkreten Typ verwenden, und geben sie Ihren Code einen "Junction Point" Sie in splice können für Unit-Tests. Aber ist es möglich, eine Schnittstelle mit asynchronen Methoden erstellen?

Die Antwort lautet „Ja“. Der folgende Code definiert eine Schnittstelle mit einer asynchronen Methode:

public interface IMyService
{
  Task<int> DownloadAndCountBytesAsync(string url);
}

Die Service-Implementierung ist einfach:

public sealed class MyService : IMyService
{
  public async Task<int> DownloadAndCountBytesAsync(string url)
  {
    await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
    using (var client = new HttpClient())
    {
      var data = await 
        client.GetByteArrayAsync(url).ConfigureAwait(false);
      return data.Length;
    }
  }
}

Abbildung 1 zeigt, wie der Code, der den Dienst nutzt die asynchrone Methode definiert auf der Schnittstelle aufruft.

Abbildung 1 UseMyService.cs: Die Async-Methode für die Schnittstelle definiert

public sealed class UseMyService
{
  private readonly IMyService _service;
  public UseMyService(IMyService service)
  {
    _service = service;
  }
  public async Task<bool> IsLargePageAsync(string url)
  {
    var byteCount = 
      await _service.DownloadAndCountBytesAsync(url);
    return byteCount > 1024;
  }
}

Dies mag wie ein allzu vereinfachende Beispiel, aber es zeigt einige wichtigen Lektionen über asynchrone Methoden.

Die erste Lektion ist: Methoden sind nicht awaitable, Arten. Es ist der Typ eines Ausdrucks, der bestimmt, ob dieser Ausdruck awaitable ist. Insbesondere erwartet UseMyService.IsLargePageAsync das Ergebnis der IMyService.DownloadAndCountBytesAsync. Die Schnittstellenmethode ist nicht (und dürfen) markierten Async. IsLargePageAsync können erwarten, da die Schnittstellenmethode eine Aufgabe gibt, und Aufgaben awaitable sind.

Die zweite Lektion lautet: Async ist ein Implementierungsdetail. UseMyService weder kennt noch kümmert, ob die Schnittstellenmethoden mithilfe der Async oder nicht implementiert werden. Der verwendete Code kümmert sich nur, dass die Methode eine Aufgabe gibt. Mit Async und erwarten ist eine gängige Methode, eine Aufgabe zurückgeben-Methode implementieren, aber es ist nicht die einzige Möglichkeit. Z. B. der Code in Abbildung 2 verwendet ein gemeinsames Muster für asynchrone Methoden überladen.

Abbildung 2 AsyncOverloadExample.cs: Verwenden ein gemeinsames Muster für asynchrone Methoden überladen

class AsyncOverloadExample
{
  public async Task<int> 
    RetrieveAnswerAsync(CancellationToken cancellationToken)
  {
    await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
    return 42;
  }
  public Task<int> RetrieveAnswerAsync()
  {
    return RetrieveAnswerAsync(CancellationToken.None);
  }
}

Beachten Sie, dass eine Überladung nur ruft der anderen und gibt seine Aufgabe direkt. Es ist möglich, schreiben Sie diese Überladung mit Async und erwarten, aber das würde nur erhöhen den overhead und bieten keinen Vorteil.

Asynchrone Unit-Tests

Gibt es andere Optionen für die Implementierung von Methoden des Task zurückgeben. Task.FromResult ist eine gemeinsame Wahl für Unit-Test-Stubs, denn es ist der einfachste Weg, eine abgeschlossene Aufgabe zu erstellen. Der folgende Code definiert eine Stubimplementierung des Dienstes:

class MyServiceStub : IMyService
{
  public int DownloadAndCountBytesAsyncResult { get; set; }
  public Task<int> DownloadAndCountBytesAsync(string url)
  {
    return Task.FromResult(DownloadAndCountBytesAsyncResult);
  }
}

Können Sie diese Stubimplementierung um zu testen, UseMyService, siehe Abbildung 3.

Abbildung 3 UseMyServiceUnitTests.cs: Stubimplementierung, Test UseMyService

[TestClass]
public class UseMyServiceUnitTests
{
  [TestMethod]
  public async Task UrlCount1024_IsSmall()
  {
    IMyService service = new MyServiceStub { 
      DownloadAndCountBytesAsyncResult = 1024 
    };
    var logic = new UseMyService(service);
    var result = await 
      logic.IsLargePageAsync("http://www.example.com/");
    Assert.IsFalse(result);
  }
  [TestMethod]
  public async Task UrlCount1025_IsLarge()
  {
    IMyService service = new MyServiceStub { 
      DownloadAndCountBytesAsyncResult = 1025 
    };
    var logic = new UseMyService(service);
    var result = await 
      logic.IsLargePageAsync("http://www.example.com/");
    Assert.IsTrue(result);
  }
}

Dieser Beispielcode verwendet MSTest, aber die meisten anderen modernen Unit-Test-Frameworks unterstützen auch asynchrone Komponententests. Stellen Sie einfach Ihr Gerät sicher Tests zurück Aufgabe; vermeiden Sie Async void Unit-Test-Methoden. Die meisten Unit-Test-Frameworks unterstützen nicht Async void Unit-Test-Methoden.

Beim synchronen Prüfverfahren Einheit, es wichtig ist, testen Sie verhält sich wie der Code sowohl in Erfolgs- und Bedingungen. Asynchrone Methoden fügen ein Problemchen: Es ist möglich für einen asynchronen Dienst erfolgreich zu sein oder eine Ausnahme, synchron oder asynchron. Sie können alle vier dieser Kombinationen testen, wenn Sie wollen, aber ich es in der Regel ausreichend finde, um zumindest asynchrone Erfolg und asynchrone Fehler sowie synchrone Erfolg ggf. zu testen. Der synchrone Erfolg-Test ist nützlich, da der Await-Operator anders handeln wird, wenn der Vorgang bereits abgeschlossen wurde. Ich finden nicht jedoch den synchrone Fehler-Test als hilfreich, weil Fehler nicht unmittelbar mit den meisten asynchronen Operationen.

Während ich dies schreibe, gibt einige beliebte Spott und stubbing Frameworks default(T) zurück, wenn Sie dieses Verhalten ändern. Karrikatur Verhalten standardmäßig funktioniert nicht gut mit asynchronen Methoden da asynchrone Methoden eine null Aufgabe nie zurückgeben soll (nach dem Task-basierte asynchrone Muster, die finden Sie unter bit.ly/1ifhkK2). Das richtige Standardverhalten wäre Task.FromResult(default(T)) zurück. Dies ist ein häufiges Problem bei Komponententests asynchronen Code; Wenn Sie unerwartete problemlos in Ihren Tests sehen, sicherzustellen Sie, dass die mock Typen alle Aufgabe zurückgebende Methoden implementieren. Ich hoffe, dass Spott und stubbing Frameworks Async-bewusst zu werden und in der Zukunft und bessere Standardverhalten für asynchrone Methoden zu implementieren.

Asynchrone Fabriken

Die Muster haben so weit wie definieren Sie eine Schnittstelle mit einer asynchronen Methode gezeigt; wie es in einem Dienst implementiert; und wie man einen Stub für Testzwecke zu definieren. Dies sind ausreichend für die meisten asynchronen Dienste, aber es gibt eine ganz andere Ebene der Komplexität, die gilt, wenn eine Service-Implementierung asynchroner einiges tun muss, bevor es verwendet werden kann. Ich möchte beschreiben, wie mit die Situation umzugehen, wo Sie einen asynchronen Konstruktor benötigen.

Konstruktoren Async hingegen nicht statische Methoden können. Eine Möglichkeit, ein Betrüger Konstruktor eines asynchrones ist eine asynchrone Factory-Methode zu implementieren siehe Abbildung 4.

Abbildung 4-Service mit einer asynchronen Factorymethode

interface IUniversalAnswerService
{
  int Answer { get; }
}
class UniversalAnswerService : IUniversalAnswerService
{
  private UniversalAnswerService()
  {
  }
  private async Task InitializeAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
    Answer = 42;
  }
  public static async Task<UniversalAnswerService> CreateAsync()
  {
    var ret = new UniversalAnswerService();
    await ret.InitializeAsync();
    return ret;
  }
  public int Answer { get; private set; }
}

Ich mag den asynchrone Factory-Ansatz, weil es kann nicht missbraucht werden. Aufrufenden Code kann nicht direkt den Konstruktor aufzurufen; Es muss die Factory-Methode verwenden, um eine Instanz abzurufen, und die Instanz wird vollständig initialisiert, bevor er zurückgegeben wird. Jedoch kann dies in einigen Szenarios verwendet werden. Während ich dies schreibe, Inversion of Control (IoC) und Dependency Injection (DI) Frameworks verstehen keine Konventionen für asynchrone Factory-Methoden. Wenn Sie Ihre Dienste mit einem IoC/DI-Container Spritzen sind, benötigen Sie einen alternativen Ansatz.

Asynchrone Ressourcen

In einigen Fällen ist asynchrone Initialisierung nur einmal erforderlich, um freigegebene Ressourcen zu initialisieren. Stephen Toub entwickelt ein Async­Lazy < T > Typ (bit.ly/1cVC3nb), das auch als ein Teil meiner AsyncEx-Bibliothek erhältlich ist (bit.ly/1iZBHOW). AsyncLazy < T > Lazy < T > kombiniert mit Aufgabe < T >. Es handelt sich hierbei um ein Lazy < < T >> Aufgabe, ein fauler Typ, asynchrone Factory-Methoden unterstützt. Die Lazy < T > Ebene bietet threadsicher verzögerte Initialisierung, sicherzustellen, dass die Factory-Methode nur einmal ausgeführt wird; die Aufgabe < T > Ebene unterstützt asynchrone, schönem Anrufern asynchron warten, bis die Fabrik-Methode abgeschlossen.

Abbildung 5 präsentiert eine etwas vereinfachte Definition der AsyncLazy < T >. Abbildung 6 zeigt, wie AsyncLazy < T > kann innerhalb eines Typs verwendet werden.

Abbildung 5-Definition der AsyncLazy < T >

// Provides support for asynchronous lazy initialization.
// This type is fully thread-safe.
public sealed class AsyncLazy<T>
{
  private readonly Lazy<Task<T>> instance;
  public AsyncLazy(Func<Task<T>> factory)
  {
    instance = new Lazy<Task<T>>(() => Task.Run(factory));
  }
  // Asynchronous infrastructure support.
// Permits instances of this type to be awaited directly.
public TaskAwaiter<T> GetAwaiter()
  {
    return instance.Value.GetAwaiter();
  }
}

Abbildung 6 AsyncLazy < T > Verwendet in einem Typ

class MyServiceSharingAsyncResource
{
  private static readonly AsyncLazy<int> _resource =
    new AsyncLazy<int>(async () =>
    {
       await Task.Delay(TimeSpan.FromSeconds(2));
       return 42;
    });
  public async Task<int> GetAnswerTimes2Async()
  {
    int answer = await _resource;
    return answer * 2;
  }
}

Dieser Dienst definiert eine einzelne freigegebene "Ressource", die asynchron konstruiert werden muss. Alle Methoden von Instanzen dieses Dienstes können diese Ressource abhängig und erwarten sie direkt. Zum ersten Mal die AsyncLazy < T > Instanz wird erwartet, es startet die asynchronen Factory-Methode einmal auf einem Threadpoolthread. Alle anderen gleichzeitigen Zugriff auf dieselbe Instanz aus einem anderen Thread warten, bis die asynchrone Factorymethode an den Threadpool in die Warteschlange gestellt hat.

Der synchrone, Thread-sicheren Teil des AsyncLazy < T > Behav­Ior erfolgt durch die Lazy < T > Schicht. Die Blockierung der Zeitaufwand ist sehr kurz: Jeder Thread wartet nur auf die Fabrik-Methode, um in der Warteschlange für den Threadpool; Sie warten nicht, bis es ausgeführt. Einmal die Aufgabe < T > die Factory-Methode, dann die Lazy < T > zurück Ebene Stelle ist vorbei. Die gleiche Aufgabe < T > Instanz wird mit jedem Await geteilt. Weder die asynchrone Factory-Methoden als auch die asynchrone verzögerte Initialisierung wird je eine Instanz von T ausgesetzt, bis der asynchrone Initialisierung abgeschlossen ist. Dies schützt vor versehentlichen Missbrauch des Typs.

AsyncLazy < T > eignet sich hervorragend für eine bestimmte Art von Problem: asynchrone Initialisierung einer freigegebenen Ressource. Es kann jedoch umständlich in anderen Szenarios verwendet. Insbesondere, wenn eine Dienstinstanz einen asynchronen Konstruktor benötigt, können Sie definieren einen "innere" Service-Typ, der die asynchrone Initialisierung übernimmt und AsyncLazy < T > die innere Instanz innerhalb der "äußeren" Diensttyp umbrochen. Aber das führt zu umständlich und langwierig Code mit allen Methoden abhängig von der gleichen inneren Instanz. In solchen Szenarien wäre eine echte "asynchrone Constructor" eleganter.

Ein Fehltritt

Bevor ich auf meine bevorzugte Lösung eingehe, möchte ich darauf hinweisen, ein etwas gemeinsamen Fehltritt. Als Entwickler konfrontiert sind, mit asynchronen Vorgänge zu tun in einem Konstruktor (die asynchron sein kann), die Abhilfe kann so etwas wie der Code in Abbildung 7.

Abbildung 7-Abhilfe bei Async-Arbeit zu tun in einem Konstruktor

class BadService
{
  public BadService()
  {
    InitializeAsync();
  }
  // BAD CODE!!
private async void InitializeAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
    Answer = 42;
  }
  public int Answer { get; private set; }
}

Aber es gibt einige ernsthafte Probleme mit diesem Ansatz. Erstens gibt es keine Möglichkeit zu sagen, wann die Initialisierung abgeschlossen ist; Zweitens werden alle Ausnahmen von der Initialisierung in der üblichen Async void Weise häufig Absturz der Anwendung behandelt. Wenn InitializeAsync Async Aufgabe statt Async nichtig sei, würde die Situation kaum verbessert werden: Noch gäbe es keine Möglichkeit zu sagen, wenn die Initialisierung abgeschlossen und die Ausnahmen ignoriert werden würde. Es gibt ein besserer Weg!

Das asynchrone Initialisierung-Muster

Die meisten Reflexion-basierte Erstellung Code (IoC/DI Frameworks, Activator.CreateInstance und So weiter) setzt Ihren Typ verfügt über einen Konstruktor und Konstruktoren nicht asynchron sein. Wenn Sie in dieser Situation sind, sind Sie gezwungen, die Instanz zurück, die (asynchron) noch nicht initialisiert wurde. Der Zweck des Musters asynchrone Initialisierung soll ein Standardverfahren der Umgang mit dieser Situation um das Problem der nicht initialisierte Instanzen abzufedern.

Zuerst definieren Sie eine "Marker"-Schnittstelle. Wenn ein Typ asynchrone Initialisierung benötigt, wird diese Schnittstelle implementiert:

/// <summary>
/// Marks a type as requiring asynchronous initialization and
/// provides the result of that initialization.
/// </summary>
public interface IAsyncInitialization
{
  /// <summary>
  /// The result of the asynchronous initialization of this instance.
/// </summary>
  Task Initialization { get; }
}

Auf den ersten Blick eine Eigenschaft vom Typ Aufgabe fühlt sich seltsam an. Ich glaube, ist es angebracht, aber da der asynchrone Vorgang (Initialisieren der Instanz) operiert auf Instanzebene ist. So bezieht sich die Initialisierungseigenschaft auf die Instanz als Ganzes.

Wenn ich diese Schnittstelle implementieren, ich bevorzuge dazu mit einer tatsächlichen Async-Methode, die ich als InitializeAsync gemäß der Konvention nennen Abbildung 8 zeigt:

Abbildung 8-Dienst implementieren der InitializeAsync-Methode

class UniversalAnswerService : 
  IUniversalAnswerService, IAsyncInitialization
{
  public UniversalAnswerService()
  {
    Initialization = InitializeAsync();
  }
  public Task Initialization { get; private set; }
  private async Task InitializeAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
    Answer = 42;
  }
  public int Answer { get; private set; }
}

Der Konstruktor ist ganz einfach; Es beginnt die asynchrone Initialisierung (durch Aufrufen von InitializeAsync) und dann wird die Initialisierung-Eigenschaft. Diese Initialisierung-Eigenschaft enthält die Ergebnisse der InitializeAsync-Methode: nach Abschluss des InitializeAsync der Initialisierung-Vorgang abgeschlossen ist, und wenn Fehler vorhanden sind, werden sie durch die Aufgabe der Initialisierung aufgetaucht sein.

Nach Abschluss der Konstruktor kann die Initialisierung noch nicht abgeschlossen ist, sein der verwendete Code ist allerdings Vorsicht. Der Code, mit dem Service hat die Verantwortung, um sicherzustellen, dass die Initialisierung abgeschlossen ist, bevor Sie andere Methoden aufrufen. Der folgende Code erstellt und initialisiert eine Dienstinstanz:

async Task<int> AnswerTimes2Async()
{
  var service = new UniversalAnswerService();
  // Danger!
The service is uninitialized here; "Answer" is 0!
await service.Initialization;
  // OK, the service is initialized and Answer is 42.
return service.Answer * 2;
}

In einem realistischeren Szenario mit IoC/DI der verwendete Code nur Ruft eine Instanz, die Durchführung von IUniversalAnswerService und testen, ob es IAsyncInitialization implementiert hat. Dies ist eine nützliche Technik; Es ermöglicht die asynchrone Initialisierung ein Implementierungsdetail des Typs sein. Beispielsweise werden Stub-Typen wahrscheinlich nicht asynchrone Initialisierung verwenden, (es sei denn, Sie tatsächlich testen, dass der verwendete Code darauf wartet, dass der Dienst initialisiert werden). Der folgende Code ist ein realistischer Gebrauch von meiner Antwort-Service:

async Task<int> 
  AnswerTimes2Async(IUniversalAnswerService service)
{
  var asyncService = service as IAsyncInitialization;
  if (asyncService != null)
    await asyncService.Initialization;
  return service.Answer * 2;
}

Bevor Sie fortfahren mit dem Muster für asynchrone Initialisierung, möchte ich eine wichtige Alternative hinweisen. Es ist möglich, die Service-Mitglieder als asynchrone Methoden verfügbar zu machen, die intern die Initialisierung des eigenen Objekte erwarten. Abbildung 9 zeigt, was diese Art von Objekt aussehen würde.

Abbildung 9-Dienst, der eine eigene Initialisierung erwartet

class UniversalAnswerService
{
  private int _answer;
  public UniversalAnswerService()
  {
    Initialization = InitializeAsync();
  }
  public Task Initialization { get; private set; }
  private async Task InitializeAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(2));
    _answer = 42;
  }
  public Task<int> GetAnswerAsync()
  {
    await Initialization;
    return _answer;
  }
}

Ich mag diesen Ansatz, weil es nicht möglich, ein Objekt zu missbrauchen, die noch noch nicht initialisiert wurde. Allerdings schränkt es die API des Dienstes, denn jedes Mitglied, das bei der Initialisierung abhängt als eine asynchrone Methode entlarvt werden muss. Im vorhergehenden Beispiel wurde die Answer-Eigenschaft mit einer GetAnswerAsync-Methode ersetzt.

Komponieren das asynchrone Initialisierung-Muster

Sagen wir, ich bin einen Service Definition, der verschiedene andere Dienste abhängen. Wenn ich das asynchrone Initialisierung-Muster für meine Dienste einführen, kann diese Dienste asynchrone Initialisierung erfordern. Der Code zur Überprüfung, ob diese Dienste IAsyncInitialization Implementieren kann etwas langweilig, aber ich kann leicht einen Helfer-Typ definieren:

public static class AsyncInitialization
{
  public static Task 
    EnsureInitializedAsync(IEnumerable<object> instances)
  {
    return Task.WhenAll(
      instances.OfType<IAsyncInitialization>()
        .Select(x => x.Initialization));
  }
  public static Task EnsureInitializedAsync(params object[] instances)
  {
    return EnsureInitializedAsync(instances.AsEnumerable());
  }
}

Die Hilfsmethoden nehmen Sie eine beliebige Anzahl von Instanzen eines beliebigen Typs, herausfiltern, die nicht IAsyncInitialization Implementieren und warten dann asynchron die Initialisierungsaufgaben abgeschlossen.

Mit diesen Hilfsmethoden im Ort ist das Erstellen eines zusammengesetzten Dienstes einfach. Der Service im Abbildung 10 nimmt zwei Instanzen des Dienstes Antwort als Abhängigkeiten, und ihre Ergebnisse im Durchschnitt.

Abbildung 10-Dienst, die Ergebnisse des Antwort-Service als Abhängigkeiten im Durchschnitt

interface ICompoundService
{
  double AverageAnswer { get; }
}
class CompoundService : ICompoundService, IAsyncInitialization
{
  private readonly IUniversalAnswerService _first;
  private readonly IUniversalAnswerService _second;
  public CompoundService(IUniversalAnswerService first,
    IUniversalAnswerService second)
  {
    _first = first;
    _second = second;
    Initialization = InitializeAsync();
  }
  public Task Initialization { get; private set; }
  private async Task InitializeAsync()
  {
    await AsyncInitialization.EnsureInitializedAsync(_first, _second);
    AverageAnswer = (_first.Answer + _second.Answer) / 2.0;
  }
  public double AverageAnswer { get; private set; }
}

Es gibt ein paar wichtige Vorteile im Auge zu behalten, wenn Dienstleistungen zu komponieren. Da asynchrone Initialisierung ein Implementierungsdetail ist, wissen nicht einerseits komponierte Dienst, ob eine Abhänigkeit asynchrone Initialisierung erfordern. Wenn keine Abhängigkeiten asynchrone Initialisierung, dann weder erfordern würde den zusammengesetzten Dienst. Aber weil sie nicht wissen, der zusammengesetzte-Dienst muss deklariert sich als asynchrone Initialisierung erfordern.

Don' t allzu große Sorge die Leistungsauswirkungen davon; Es werden einige zusätzliche Speicherzuordnungen für die asynchronen Strukturen, aber der Thread wird nicht asynchron Verhalten. Erwarten hat eine "schnellen Weg"-Optimierung, die ins Spiel kommt, wenn der Code eine Aufgabe erwartet, die bereits abgeschlossen ist. Wenn die Abhängigkeiten eines zusammengesetzten Dienstes asynchrone Initialisierung nicht benötigen, ist die Sequenz an Task.WhenAll übergeben leer, verursachen Task.WhenAll eine bereits abgeschlossene Aufgabe zurückgegeben. Wenn diese Aufgabe von CompoundService.InitializeAsync erwartet wird, wird nicht sie Ausführung bringen, weil die Aufgabe bereits erledigt hat. In diesem Szenario schließt InitializeAsync synchron, bevor der Konstruktor abgeschlossen ist.

Eine zweite Essen ist ist es wichtig, alle Abhängigkeiten zu initialisieren, bevor die zusammengesetzte InitializeAsync zurückgibt. Dadurch wird sichergestellt, dass die zusammengesetzten Typ Initialisierung vollständig abgeschlossen ist. Fehlerbehandlung ist auch natürliche — wenn ein abhängiger Dienst einen Fehler bei der Initialisierung verfügt, diese Fehler propagieren von EnsureInitializedAsync, der zusammengesetzten Typ InitializeAsync mit dem gleichen Fehler fehlschlagen verursachen.

Die endgültige Essen ist, dass der zusammengesetzte Dienst keine besondere Art des Typs ist. Es ist nur ein Dienst, der asynchrone Initialisierung, genau wie jede andere Art von Dienst unterstützt. Dieser Dienste können zum Testen, verspottet werden, ob sie asynchrone Initialisierung oder nicht unterstützen.

Zusammenfassung

Die Muster in diesem Artikel können auf jede Art von Anwendung anwenden; Ich habe sie in ASP.NET und Konsole sowie MVVM-Anwendungen verwendet. Meine eigene Lieblings Muster für asynchrone Bau ist die asynchrone Factory-Methode; Es ist sehr einfach und kann nicht durch den Verzehr von Code, weil es nie eine nicht initialisierte Instanz Macht missbraucht werden. Aber ich habe auch herausgefunden das asynchrone Initialisierung-Muster sehr nützlich bei der Arbeit in Szenarien, in denen ich nicht (oder will) meine eigene Instanzen zu erstellen. Die AsyncLazy < T > Muster hat auch ihren Platz, wenn gibt es gemeinsam genutzte Ressourcen, die asynchrone Initialisierung erfordern.

Der asynchrone Dienst-Muster sind mehr etabliert als das MVVM-Muster, die ich früher in dieser Serie vorgestellt. Das Muster für asynchrone Datenbindung und die verschiedenen Ansätze für asynchrone Befehle sind beide ziemlich neu, und sie haben sicherlich Verbesserungsbedarf. Die Muster der asynchrone Dienst und haben im Gegensatz dazu mehr allgemein verwendet worden. Es gelten jedoch die üblichen Vorbehalte: Diese Muster sind nicht Evangelium; Sie sind gerade Techniken, die nützlich fand ich habe und wollte zu teilen. Wenn Sie können sie verbessern oder sie an Ihre Anwendungsanforderungen passen, geh rechts weiter. Ich hoffe, dieser Artikel hilfreich für asynchrone MVVM-Muster und, noch mehr vorstellen, dass sie ermutigt habe, Sie erweitern sie und entdecken Sie Ihre eigenen asynchrone Muster für UIs gewesen sein.

Stephen Cleary lebt als Ehemann, Vater und Entwickler in den USA im Norden von Michigan. Seit 16 Jahren beschäftigt er sich mit Multithreading und asynchroner Programmierung und nutzt die async-Unterstützung in Microsoft .NET Framework seit der ersten CTP-Version. Seine Homepage und seinen Blog finden Sie unter stephencleary.com.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: James McCaffrey und Stephen Toub