2013-12-13 22 views
13

Ich nehme an, dass die asynchronen Methoden gut für IO-Arbeit sind, weil sie den Thread nicht blockieren, während sie erwartet werden, aber wie ist das eigentlich möglich? Ich nehme an, dass etwas zuhören muss, um die Aufgabe auszulösen, also bedeutet das, dass die Blockierung nur woanders hin verschoben wird?Wie erwartet async-erwartet nicht blockieren?

Antwort

20

Nein, die Blockierung wird nirgendwo anders verschoben. BCL-Methoden, die erwartete Typen zurückgeben, verwenden Techniken wie überlappende I/O mit I/O-Completion-Ports für eine vollständig asynchrone Erfahrung.

Ich habe eine recent blog post, die beschreibt, wie dies den ganzen Weg hinunter zum physischen Gerät und zurück funktioniert.

+4

Cool! Es ist lustig, dass ich deinen Blog gelesen habe, als ich an diese Frage dachte. Sieht so aus, als müsste ich ALLE PUNKTE lesen, bevor ich wieder auf den Stackoverflow gehe! – NickL

+1

@NickL, du bist nicht alleine.:) –

11

Async-await schreibt Ihren Code tatsächlich für Sie neu. Eine Task Continuation wird verwendet, und diese Fortsetzung wird in den Synchronisierungskontext übernommen, der beim Erstellen der Fortsetzung aktuell war.

So ist die folgende Funktion

public async Task Example() 
{ 
    Foo(); 
    string barResult = await BarAsync(); 
    Baz(barResult); 
} 

in so etwas wie gedreht Gets (aber nicht genau) diese

public Task Example() 
{ 
    Foo(); 
    var syncContext = SyncronizationContext.Current; 
    return BarAsync().ContinueWith((continuation) => 
        { 
         Action postback =() => 
         { 
          string barResult = continuation.Result(); 
          Baz(barResult) 
         } 

         if(syncContext != null) 
          syncContext.Post(postback, null); 
         else 
          Task.Run(postback); 
        }); 
} 

Nun ist es tatsächlich viel komplizierter als das, aber das ist der grundlegende Kern davon.


Was wirklich geschieht, ist es es die Funktion aufruft GetAwaiter() wenn es existiert und tut etwas mehr wie dieses

public Task Example() 
{ 
    Foo(); 
    var task = BarAsync(); 
    var awaiter = task.GetAwaiter(); 

    Action postback =() => 
    { 
     string barResult = awaiter.GetResult(); 
     Baz(barResult) 
    } 


    if(awaiter.IsCompleted) 
     postback(); 
    else 
    { 
     var castAwaiter = awaiter as ICriticalNotifyCompletion; 
     if(castAwaiter != null) 
     { 
      castAwaiter.UnsafeOnCompleted(postback); 
     } 
     else 
     { 
      var context = SynchronizationContext.Current; 

      if (context == null) 
       context = new SynchronizationContext(); 

      var contextCopy = context.CreateCopy(); 

      awaiter.OnCompleted(() => contextCopy.Post(postback, null)); 
     } 
    } 
    return task; 
} 

Diese noch nicht genau das, was passiert, aber das Wichtigste weg zu nehmen ist, Wenn awaiter.IsCompleted wahr ist, wird der Postback-Code synchron ausgeführt, anstatt einfach sofort zurückzukehren.

Die kühle Sache ist, Sie müssen nicht auf eine Aufgabe warten, können Sie await anything solange es eine Funktion GetAwaiter() und das zurückgegebene Objekt aufgerufen hat, kann die folgende Signatur

public class MyAwaiter<TResult> : INotifyCompletion 
{ 
    public bool IsCompleted { get { ... } } 
    public void OnCompleted(Action continuation) { ... } 
    public TResult GetResult() { ... } 
} 
//or 
public class MyAwaiter : INotifyCompletion 
{ 
    public bool IsCompleted { get { ... } } 
    public void OnCompleted(Action continuation) { ... } 
    public void GetResult() { ... } 
} 

erfüllen

Auf dem fortlaufenden Abenteuer auf making my wrong answer even more wrong, hier ist der tatsächliche dekompilierte Code, den der Compiler meine Beispielfunktion in umstellt.

[DebuggerStepThrough, AsyncStateMachine(typeof(Form1.<Example>d__0))] 
public Task Example() 
{ 
    Form1.<Example>d__0 <Example>d__; 
    <Example>d__.<>4__this = this; 
    <Example>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); 
    <Example>d__.<>1__state = -1; 
    AsyncTaskMethodBuilder <>t__builder = <Example>d__.<>t__builder; 
    <>t__builder.Start<Form1.<Example>d__0>(ref <Example>d__); 
    return <Example>d__.<>t__builder.Task; 
} 

Nun, wenn Sie durch dort schauen, werden Sie sehen, dass es keinen Hinweis auf Foo() ist, BarAsync() oder Baz(barResult) dies liegt daran, dass, wenn Sie async der Compiler tatsächlich nutzen Ihre Funktion in eine state machine verwandelt sich auf der Grundlage der IAsyncStateMachine Schnittstelle. Wenn wir uns zu gehen, erzeugt der Compiler eine neue Struktur <Example>d__0 genannt bei ILSpy

[CompilerGenerated] 
[StructLayout(LayoutKind.Auto)] 
private struct <Example>d__0 : IAsyncStateMachine 
{ 
    public int <>1__state; 
    public AsyncTaskMethodBuilder <>t__builder; 
    public Form1 <>4__this; 
    public string <barResult>5__1; 
    private TaskAwaiter<string> <>u__$awaiter2; 
    private object <>t__stack; 
    void IAsyncStateMachine.MoveNext() 
    { 
     try 
     { 
      int num = this.<>1__state; 
      if (num != -3) 
      { 
       TaskAwaiter<string> taskAwaiter; 
       if (num != 0) 
       { 
        this.<>4__this.Foo(); 
        taskAwaiter = this.<>4__this.BarAsync().GetAwaiter(); 
        if (!taskAwaiter.IsCompleted) 
        { 
         this.<>1__state = 0; 
         this.<>u__$awaiter2 = taskAwaiter; 
         this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Form1.<Example>d__0>(ref taskAwaiter, ref this); 
         return; 
        } 
       } 
       else 
       { 
        taskAwaiter = this.<>u__$awaiter2; 
        this.<>u__$awaiter2 = default(TaskAwaiter<string>); 
        this.<>1__state = -1; 
       } 
       string arg_92_0 = taskAwaiter.GetResult(); 
       taskAwaiter = default(TaskAwaiter<string>); 
       string text = arg_92_0; 
       this.<barResult>5__1 = text; 
       this.<>4__this.Baz(this.<barResult>5__1); 
      } 
     } 
     catch (Exception exception) 
     { 
      this.<>1__state = -2; 
      this.<>t__builder.SetException(exception); 
      return; 
     } 
     this.<>1__state = -2; 
     this.<>t__builder.SetResult(); 
    } 
    [DebuggerHidden] 
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0) 
    { 
     this.<>t__builder.SetStateMachine(param0); 
    } 
} 

Dank der Menschen über für die Herstellung ihrer Werkzeug eine Bibliothek verwenden, die Sie erweitern können und sich von Code aufrufen. Um den obigen Code zu bekommen, musste ich nur folgendes tun:

+0

"Okay, das bedeutet nur, dass die Blockierung in BarAsync() erfolgt." Ich weiß, du hast recht, aber deine Antwort erklärt nicht warum, während Stephen Cleary es tut. – hvd

+0

@ m59 Keiner der Dinge, die Sie gerade in Code-Blöcke stecken, sind tatsächliche Blöcke von Code ... – Servy

+0

@Servy oops, Entschuldigung. Ich dachte, sie bezogen sich auf eine Funktion. Es scheint immer noch so zu sein, als sollten sie auffallen ... Gibt es etwas Besseres, um das hervorzuheben? – m59

Verwandte Themen