在异步方法中调用ConfigureAwait(false)是否有更可读的替代方法?

时间:2014-11-24 16:18:11

标签: c# async-await code-formatting

我目前正在撰写大量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

在我看来,它开始使代码的可读性低得多。

我的问题基本上是这样的:有没有办法进一步减少这种情况(除了缩短扩展方法的名称)?

3 个答案:

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