2013-12-13 104 views
13

我認爲異步方法對IO工作很好,因爲它們在等待期間不會阻塞線程,但這實際上可能如何?我假設某些東西必須傾聽才能觸發任務完成,那麼這是否意味着阻止只是移動到別的地方?async-await如何不阻塞?

回答

20

不,阻塞不會移動到其他地方。返回等待類型的BCL方法使用諸如與I/O完成端口重疊的I/O之類的技術來獲得完全異步的體驗。

我有一個recent blog post,它描述了這種方式如何一直工作到物理設備並返回。

+4

很酷!當我想到這個問題時,我實際上正在閱讀您的博客很有趣。看起來我必須閱讀所有你的帖子,然後再次進入stackoverflow! – NickL

+1

@NickL,你並不孤單。:) –

11

異步等待實際上是爲你重寫你的代碼。它所做的是使用任務延續,並將該延續重新放回到創建延續時的當前同步上下文中。

所以下面的功能

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

獲取上繳類似(但不完全)這個

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

現在,它實際上是一個很多比這更復雜,但是這是基本的要點的。


什麼是真正的情況是,如果它存在但更多的東西像這樣

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

這仍然是不完全會發生什麼,但重要的是要就是帶走它,它調用函數GetAwaiter()如果awaiter.IsCompleted爲真,它將同步運行回發代碼而不是立即返回。

很酷的事情是,你不需要等待一個任務,你可以await anything,只要它有一個名爲GetAwaiter()功能和返回的對象可以實現以下籤​​名

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() { ... } 
} 

making my wrong answer even more wrong上繼續冒險,這裏是編譯器將我的示例函數轉換爲的實際反編譯代碼。

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

現在,如果你通過那裏你會看到有到Foo()BarAsync(),或Baz(barResult)沒有提到這是因爲當你使用async編譯器實際上是在以基於IAsyncStateMachine接口的state machine變成你的函數。如果我們去看看,編譯器生成一個新的結構叫做<Example>d__0

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

多虧了人過來在ILSpy爲使他們的工具使用,你可以擴展和從自己的代碼調用的庫。要得到上面的代碼,我所要做的只是

using System.IO; 
using ICSharpCode.Decompiler; 
using ICSharpCode.Decompiler.Ast; 
using Mono.Cecil; 

namespace Sandbox_Console 
{ 
    internal class Program 
    { 
     public static void Main() 
     { 
      AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(@"C:\Code\Sandbox Form\SandboxForm\bin\Debug\SandboxForm.exe"); 
      var context = new DecompilerContext(assembly.MainModule); 
      context.Settings.AsyncAwait = false; //If you don't do this it will show the original code with the "await" keyword and hide the state machine. 
      AstBuilder decompiler = new AstBuilder(context); 
      decompiler.AddAssembly(assembly); 

      using (var output = new StreamWriter("Output.cs")) 
      { 
       decompiler.GenerateCode(new PlainTextOutput(output)); 
      } 
     } 
    } 
} 
+0

「好的,所以這只是意味着阻塞在BarAsync()中完成。」我知道你是對的,但你的回答並不能解釋爲什麼,而斯蒂芬·克利裏的確如此。 – hvd

+0

@ m59你剛剛放在代碼塊中的東西都不是實際的代碼塊...... – Servy

+0

@Servy oops,道歉。我認爲他們是指一個功能。看起來他們似乎應該脫穎而出......有沒有更好的方法來突出這一點? – m59