我認爲異步方法對IO工作很好,因爲它們在等待期間不會阻塞線程,但這實際上可能如何?我假設某些東西必須傾聽才能觸發任務完成,那麼這是否意味着阻止只是移動到別的地方?async-await如何不阻塞?
13
A
回答
20
不,阻塞不會移動到其他地方。返回等待類型的BCL方法使用諸如與I/O完成端口重疊的I/O之類的技術來獲得完全異步的體驗。
我有一個recent blog post,它描述了這種方式如何一直工作到物理設備並返回。
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));
}
}
}
}
相關問題
- 1. 阻塞或不阻塞(Express.js)
- 2. 如何阻止NpgsqlDataReader阻塞?
- 3. 阻塞隊列不阻塞線程?
- 4. ESI是否阻塞或不阻塞?
- 5. mpi:阻塞與非阻塞
- 6. 阻止阻塞的線程阻塞
- 7. 舞者:如何在不阻塞
- 8. 如何無阻塞GC
- 9. 如何發送非阻塞
- 10. 如何使Rails非阻塞?
- 11. 如何凍結/阻塞列?
- 12. 如何避免Assembly.LoadFrom阻塞?
- 13. 如何取消阻塞ServerSocket.accept()上阻塞的線程?
- 14. 如何修改MPI阻塞發送和接收到非阻塞
- 15. 如何在Linux中顯示進程狀態(阻塞,非阻塞)
- 16. 如何將Scala中的阻塞IO封裝爲非阻塞
- 17. 如何檢查I/O操作是阻塞還是非阻塞?
- 18. UIImagePickerController阻塞塞格
- 19. Scanner.nextLine()何時阻塞?
- 20. 帶延遲的阻塞/非阻塞
- 21. 信號量阻塞和解除阻塞
- 22. 嵌套Socket阻塞非阻塞SocketHi
- 23. 非阻塞PASV襪子和阻塞
- 24. 是renderer.render()阻塞還是非阻塞?
- 25. 非阻塞寫入和阻塞recv
- 26. AsyncAwait概念
- 27. BOOST-ASIO阻塞服務器不響應阻塞客戶端?
- 28. 阻塞和不阻塞子進程調用
- 29. 爲什麼ioctl()不阻塞?
- 30. C++ Winsock recv不會阻塞
很酷!當我想到這個問題時,我實際上正在閱讀您的博客很有趣。看起來我必須閱讀所有你的帖子,然後再次進入stackoverflow! – NickL
@NickL,你並不孤單。:) –