我目前正在撰写大量async
库代码,并且我知道在每次异步调用后添加ConfigureAwait(false)
的做法,以避免编组延续代码回到原始(通常是UI)线程上下文。由于我不喜欢未标记的布尔参数,因此我倾向于将其写为ConfigureAwait(continueOnCapturedContext: false)
。
我添加了一个扩展方法,使其更具可读性(并在某种程度上减少了输入):
public static class TaskExtensions
{
public static ConfiguredTaskAwaitable<TResult> WithoutCapturingContext<TResult>(this Task<TResult> task)
{
return task.ConfigureAwait(continueOnCapturedContext: false);
}
public static ConfiguredTaskAwaitable WithoutCapturingContext(this Task task)
{
return task.ConfigureAwait(continueOnCapturedContext: false);
}
}
所以现在我可以使用await SomethingAsync().WithoutCapturingContext()
而不是await SomethingAsync().ConfigureAwait(continueOnCapturedContext: false)
。我认为这是一种改进,但是当我必须在相同的代码块中调用多个async
方法时,即使这种情况开始变得很重要,因为我最终得到了类似的东西:
await FooAsync().WithoutCapturingContext();
var bar = await BarAsync().WithoutCapturingContext();
await MoreFooAsync().WithoutCapturingContext();
var moreBar = await MoreBarAsync().WithoutCapturingContext();
// etc, etc
在我看来,它开始使代码的可读性低得多。
我的问题基本上是这样的:有没有办法进一步减少这种情况(除了缩短扩展方法的名称)?
答案 0 :(得分:8)
没有全局设置可以阻止方法中的任务捕获同步上下文,但可以做的是仅更改该方法范围的同步上下文。在您的特定情况下,您可以将上下文更改为默认同步上下文,仅适用于该方法的范围。
编写一个简单的一次性类可以很容易地改变同步上下文,然后在处理时将其更改回来:
public class SyncrhonizationContextChange : IDisposable
{
private SynchronizationContext previous;
public SyncrhonizationContextChange(SynchronizationContext newContext = null)
{
previous = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(newContext);
}
public void Dispose()
{
SynchronizationContext.SetSynchronizationContext(previous);
}
}
允许你写:
using(var change = new SyncrhonizationContextChange())
{
await FooAsync();
var bar = await BarAsync();
await MoreFooAsync();
var moreBar = await MoreBarAsync();
}
(注意将上下文设置为null
意味着它将使用默认上下文。)
答案 1 :(得分:4)
简短的回答是否定的。
每次必须单独使用ConfigureAwait
,没有全局配置或类似的东西。正如您所说,您可以使用名称较短的扩展方法,但这并没有太大变化。您可以对您的代码实施某种转换(可能使用Roslyn),这种转换会将ConfigureAwait(false)
粘贴到任何地方,但在我看来它是不可靠的。最后,它只是您需要时编写的内容,例如await
或;
。
答案 2 :(得分:4)
请注意,ConfigureAwait(false)
并不意味着忽略同步上下文。有时,它可以 push the await
continuation to a pool thread ,尽管在具有非空同步上下文的非池线程上触发了实际的延续。 IMO,ConfigureAwait(false)
的这种行为可能令人惊讶且不直观。至少,它的副作用是冗余线程切换。
如果你真的想在await
之后忽略continuation线程的同步上下文,并且只是在线程/上下文发生的情况下同步恢复执行(TaskContinuationOptions.ExecuteSynchronously
),你可以使用自定义awaiter:
await MoreFooAsync().IgnoreContext();
以下是IgnoreContext
的可能实现(仅经过非常轻微的测试):
public static class TaskExt
{
// Generic Task<TResult>
public static IgnoreContextAwaiter<TResult> IgnoreContext<TResult>(this Task<TResult> @task)
{
return new IgnoreContextAwaiter<TResult>(@task);
}
public struct IgnoreContextAwaiter<TResult> :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
Task<TResult> _task;
public IgnoreContextAwaiter(Task<TResult> task)
{
_task = task;
}
// custom Awaiter methods
public IgnoreContextAwaiter<TResult> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _task.IsCompleted; }
}
public TResult GetResult()
{
// result and exceptions
return _task.GetAwaiter().GetResult();
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
// not always synchronous, http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx
_task.ContinueWith(_ => continuation(), TaskContinuationOptions.ExecuteSynchronously);
}
// ICriticalNotifyCompletion
public void UnsafeOnCompleted(Action continuation)
{
// why SuppressFlow? http://blogs.msdn.com/b/pfxteam/archive/2012/02/29/10274035.aspx
using (ExecutionContext.SuppressFlow())
{
OnCompleted(continuation);
}
}
}
// Non-generic Task
public static IgnoreContextAwaiter IgnoreContext(this Task @task)
{
return new IgnoreContextAwaiter(@task);
}
public struct IgnoreContextAwaiter :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
{
Task _task;
public IgnoreContextAwaiter(Task task)
{
_task = task;
}
// custom Awaiter methods
public IgnoreContextAwaiter GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _task.IsCompleted; }
}
public void GetResult()
{
// result and exceptions
_task.GetAwaiter().GetResult();
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
// not always synchronous, http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx
_task.ContinueWith(_ => continuation(), TaskContinuationOptions.ExecuteSynchronously);
}
// ICriticalNotifyCompletion
public void UnsafeOnCompleted(Action continuation)
{
// why SuppressFlow? http://blogs.msdn.com/b/pfxteam/archive/2012/02/29/10274035.aspx
using (ExecutionContext.SuppressFlow())
{
OnCompleted(continuation);
}
}
}
}