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

Ste*_*nds 11 c# code-formatting async-await

我目前正在编写大量的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在我可以有类似的东西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
Run Code Online (Sandbox Code Playgroud)

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

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

Ser*_*rvy 9

没有全局设置可以阻止方法中的任务捕获同步上下文,但您可以做的是仅更改该方法范围的同步上下文.在您的特定情况下,您可以将上下文更改为默认同步上下文,仅适用于该方法的范围.

编写一个简单的一次性类可以很容易地改变同步上下文,然后在处理时将其更改回来:

public class SyncrhonizationContextChange : IDisposable
{
    private SynchronizationContext previous;
    public SyncrhonizationContextChange(SynchronizationContext newContext = null)
    {
        previous = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(newContext);
    }

    public void Dispose()
    {
        SynchronizationContext.SetSynchronizationContext(previous);
    }
}
Run Code Online (Sandbox Code Playgroud)

允许你写:

using(var change = new SyncrhonizationContextChange())
{
    await FooAsync();
    var bar = await BarAsync();
    await MoreFooAsync();
    var moreBar = await MoreBarAsync();
}
Run Code Online (Sandbox Code Playgroud)

(注意设置上下文null意味着它将使用默认上下文.)


nos*_*tio 5

请注意,ConfigureAwait(false)这并不意味着忽略同步上下文。有时,尽管实际延续已在具有非null同步上下文的非池线程上触发,但它仍可以await延续推送到池线程。海事组织,这种行为ConfigureAwait(false)可能令人惊讶且非直觉。至少,它的副作用是冗余线程切换。

如果您确实想在此之后忽略继续线程的同步上下文,await而只是在TaskContinuationOptions.ExecuteSynchronously碰巧发生的线程/上下文上同步恢复执行(),则可以使用自定义的等待者:

await MoreFooAsync().IgnoreContext();
Run Code Online (Sandbox Code Playgroud)

以下是可能的实现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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)