Schnittstellenimplementierungen asynchronisieren

116

Ich versuche derzeit, meine Anwendung mit einigen Async-Methoden zu erstellen. Alle meine E / A-Vorgänge werden durch explizite Implementierungen einer Schnittstelle ausgeführt, und ich bin etwas verwirrt darüber, wie die Vorgänge asynchronisiert werden sollen.

Aus meiner Sicht habe ich zwei Möglichkeiten bei der Implementierung:

interface IIO
{
    void DoOperation();
}

OPTION1: Führen Sie eine implizite Implementierung asynchron durch und warten Sie auf das Ergebnis in der impliziten Implementierung.

class IOImplementation : IIO
{

     async void DoOperation()
    {
        await Task.Factory.StartNew(() =>
            {
                //WRITING A FILE OR SOME SUCH THINGAMAGIG
            });
    }

    #region IIO Members

    void IIO.DoOperation()
    {
        DoOperation();
    }

    #endregion
}

OPTION2: Führen Sie die explizite Implementierung asynchron durch und warten Sie auf die Aufgabe der impliziten Implementierung.

class IOAsyncImplementation : IIO
{
    private Task DoOperationAsync()
    {
        return new Task(() =>
            {
                //DO ALL THE HEAVY LIFTING!!!
            });
    }

    #region IIOAsync Members

    async void IIO.DoOperation()
    {
        await DoOperationAsync();
    }

    #endregion
}

Ist eine dieser Implementierungen besser als die andere oder gibt es einen anderen Weg, an den ich nicht denke?

Moriya
quelle

Antworten:

230

Keine dieser Optionen ist korrekt. Sie versuchen, eine synchrone Schnittstelle asynchron zu implementieren. Tu das nicht. Das Problem ist, dass DoOperation()der Vorgang bei der Rückkehr noch nicht abgeschlossen ist. Schlimmer noch, wenn während des Vorgangs eine Ausnahme auftritt (was bei E / A-Vorgängen sehr häufig vorkommt), hat der Benutzer keine Chance, diese Ausnahme zu behandeln.

Sie müssen die Schnittstelle so ändern , dass sie asynchron ist:

interface IIO
{
    Task DoOperationAsync(); // note: no async here
}

class IOImplementation : IIO
{
    public async Task DoOperationAsync()
    {
        // perform the operation here
    }
}

Auf diese Weise sieht der Benutzer, dass der Vorgang ausgeführt wird, asyncund kann ihn ausführen await. Dies zwingt die Benutzer Ihres Codes auch dazu, zu wechseln async, aber das ist unvermeidlich.

Ich gehe auch davon aus, dass die Verwendung StartNew()in Ihrer Implementierung nur ein Beispiel ist. Sie sollten dies nicht benötigen, um asynchrone E / A zu implementieren. (Und new Task()noch schlimmer ist , das wird nicht einmal Arbeit, weil Sie nicht tun Start()das Task.)

svick
quelle
Wie würde dies bei einer expliziten Implementierung aussehen? Wo warten Sie auf diese Implementierung?
Moriya
1
@Animal Explizite Implementierung würde wie immer aussehen (einfach hinzufügen async) : async Task IIO.DoOperationAsync(). Und meinst du, wo kommst du awaitzurück Task? Wo immer Sie anrufen DoOperationAsync().
Svick
Grundsätzlich denke ich, ich kann meine Frage in "Wo warte ich?" Wenn ich nicht innerhalb der asynchronen Methode warte, erhalte ich Kompilierungswarnungen.
Moriya
1
Im Idealfall sollten Sie den E / A-Code nicht einschließen müssen Task.Run(), da der E / A-Code selbst asynchron sein sollte und Sie dies awaitdirekt tun würden . ZB line = await streamReader.ReadLineAsync().
Svick
4
Dann macht es nicht viel Sinn, Ihren Code asynchron zu machen. Siehe Artikel Soll ich asynchrone Wrapper für synchrone Methoden verfügbar machen?
Svick
19

Eine bessere Lösung besteht darin, eine weitere Schnittstelle für asynchrone Vorgänge einzuführen. Die neue Schnittstelle muss von der ursprünglichen Schnittstelle erben.

Beispiel:

interface IIO
{
    void DoOperation();
}

interface IIOAsync : IIO
{
    Task DoOperationAsync();
}


class ClsAsync : IIOAsync
{
    public void DoOperation()
    {
        DoOperationAsync().GetAwaiter().GetResult();
    }

    public async Task DoOperationAsync()
    {
        //just an async code demo
        await Task.Delay(1000);
    }
}


class Program
{
    static void Main(string[] args)
    {
        IIOAsync asAsync = new ClsAsync();
        IIO asSync = asAsync;

        Console.WriteLine(DateTime.Now.Second);

        asAsync.DoOperation();
        Console.WriteLine("After call to sync func using Async iface: {0}", 
            DateTime.Now.Second);

        asAsync.DoOperationAsync().GetAwaiter().GetResult();
        Console.WriteLine("After call to async func using Async iface: {0}", 
            DateTime.Now.Second);

        asSync.DoOperation();
        Console.WriteLine("After call to sync func using Sync iface: {0}", 
            DateTime.Now.Second);

        Console.ReadKey(true);
    }
}

PS Entwerfen Sie Ihre asynchronen Vorgänge so, dass sie Task anstelle von void zurückgeben, es sei denn, Sie müssen void wirklich zurückgeben.

Dima
quelle
5
Warum nicht GetAwaiter().GetResult()statt Wait()? Auf diese Weise müssen Sie keine entpacken AggregateException, um die innere Ausnahme abzurufen.
Tagc
Eine Variation hängt davon ab, dass die Klasse mehrere (möglicherweise explizite) Schnittstellen implementiert : class Impl : IIO, IIOAsync. IIO und IIOAsync selbst sind jedoch unterschiedliche Verträge, die verhindern können, dass "alte Verträge" in neueren Code übernommen werden. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740