在我的Asp.Net WebApi控制器(框架版本4.6.1)中,我具有以下代码:
[Route("async_test_2")]
public async Task<IHttpActionResult> AsyncTest2()
{
TelemetryDebugWriter.IsTracingDisabled = true;
var aspNetContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); //set context while calling AsyncMethod
var task = AsyncMethod();
SynchronizationContext.SetSynchronizationContext(aspNetContext); //Restore AspNet context before awaiting
DebugContext("Before outer await");
await Task.WhenAll(new Task[] { task });
DebugContext("After outer await");
return Ok();
}
private async Task AsyncMethod()
{
DebugContext("Before inner await");
await Task.Delay(2000);
DebugContext("After inner await");
}
private void DebugContext(string location)
{
System.Diagnostics.Debug.WriteLine(location + " --- SyncContext: " + (SynchronizationContext.Current?.ToString() ?? "null") + "; ManagedThreadId: " + Thread.CurrentThread.ManagedThreadId);
}
调试输出为:
Before inner await --- SyncContext: System.Threading.SynchronizationContext; ManagedThreadId: 6
Before outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 6
After inner await --- SyncContext: null; ManagedThreadId: 5
After outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 6
为什么继续“在内部等待之后”具有空的SynchronizationContext?如果我只删除对SetSynchronizationContext的调用并恢复它的调用(即,不修改上下文,保留默认的AspNetSynchronizationContext),则在任何调试输出中,该上下文都不为空。
Before inner await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 7
Before outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 7
After inner await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 8
After outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 8
在内部等待之后添加'ConfigureAwait(false)'将导致上下文在连续状态中为空。
Before inner await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 7
Before outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 7
After inner await --- SyncContext: null; ManagedThreadId: 7
After outer await --- SyncContext: System.Web.AspNetSynchronizationContext; ManagedThreadId: 8
因此,当AspNetSynchronizationContext处于活动状态时,它可以按预期方式工作,但默认的SynchronizationContext处于活动状态时,则不能按预期工作。在那种情况下,无论是否调用ConfigureAwait(false),它在连续中始终为null。
答案 0 :(得分:0)
最近,我开始学习有关SynchronizationContext的知识,在阅读.Net Framewrok源代码之后,我发现等待只是捕获当前的SynchronizationContext并将剩余的代码执行发布到上下文中,但是它没有设置SynchronizaitonContext,所以您空了。
AspNetSynchronizationContext做一些额外的工作,以确保即使任务在不同的线程中执行,SynchronizationContext.Current也相同。您可以检查 ISyncContext。输入实现:
// set synchronization context for the current thread to support the async pattern
_originalSynchronizationContext = AsyncOperationManager.SynchronizationContext;
AspNetSynchronizationContextBase aspNetSynchronizationContext = HttpContext.SyncContext;
AsyncOperationManager.SynchronizationContext = aspNetSynchronizationContext;
AsyncOperationManger.SynchronizationContext 只是调用 SynchronizationContext.SetSynchronizationContext 方法来设置 SynchronizationContext.Current
您可以签出这个要替换的SynchronizationContext简单实现
new SynchronizationContext()
使用
new CustomSyncContext()
CustomSyncContext:
public class CustomSyncContext : SynchronizationContext
{
private Task lastTask = Task.FromResult<object>(null);
private object lockObj = new object();
public override void Post(SendOrPostCallback d, object state)
{
Debug.WriteLine("post back from await");
lock (lockObj)
{
var newTask = lastTask.ContinueWith(_=>{
AsyncOperationManager.SynchronizationContext = this;
d(state);
}, TaskScheduler.Default);
lastTask = newTask;
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
并发现 SynchronizationContext.Current 不再为空
答案 1 :(得分:0)
SynchronizationContext.Current是线程本地的。 Task.Delay在新线程上运行(Tasks自动“消除线程”的说法很危险),因此它的SynchronizationContext为null。线程池中的所有线程都为空(在此处携带真实对象是不安全的-它们永远不会死,在每次运行后您都需要非常小心的清理代码,否则您会陷入邪恶的纠缠)。
该null表示将连续记录发送到线程池的主运行区,该线程将其序列化(在队列中),但不会运行它们。他是一个懒惰的混蛋:-)委派他所能做的一切。取而代之的是,当它们的运行时间到来时,他会将这些连续记录发送回自己的线程池中-并且完全不保证执行顺序(序列化只是出于自身安全考虑,是一个热点)。因此,在您运行“内部等待之后”调试打印时,SynchronizationContext再次为null,并且您处于未知线程中。
您必须编写自己的SynchronizationContext派生并管理它的实例(无锁定)。您可以将它们分配给线程池的线程,但是您必须非常小心并检测到链中最后一个延续何时完成,因为此时必须清理。本质上,您还需要自己的线程类派生类。
不要忘记线程池中的线程永远存在-线程池的整个目的是永远不必销毁它,而后再创建新的线程对象。另外,GC将所有线程对象都视为根,这意味着除非您明确地使指针无效,否则它们所接触的对象也将成为永恒的对象(某些人会称之为泄漏对象)。