多委托异步操作仅适用于Begin Invoke

时间:2018-03-08 07:17:31

标签: c# asynchronous async-await

我正在尝试理解异步操作,我有点困惑。 行动只是荣耀代表。鉴于行动

Action act = null;
act += () => { Console.WriteLine("Sync"); };
act += async () =>  { await File.AppendAllLinesAsync("C:/Test.Txt", 
                                    new[] { "Async File Operation" }); 
                     };

我们如何调用此异步,因为其中一个委托是异步而另一个不是。我已经看到其他SO answers中的一些扩展方法被简化为示例,如下所示:

public static void InvokeAsync(this Action action, AsyncCallback ar, object userObject = null)
{
    var listeners = action.GetInvocationList();
    foreach (var t in listeners)
    {
        var handler = (Action)t;
        handler.BeginInvoke(ar, userObject);
    }
}

我担心这是否有效,因为它看起来像是为每个听众调用你的回调而没有意义。

我只是使用了更友好的版本async / await的异步,所以我不太了解这种语法。 (我假设回调将是await之后的所有内容,而userObject相当于在没有ConfigureAwait(false)的情况下调用sync时导致死锁的可怕SyncronizationContext,但这只是一个猜测)

这是语法不方便所以我会使用async await语法,因为使用duck-typing调用async / await。我已经阅读了一篇关于使用async with delegates的博客,例如

public static class DelegateExtensions
{
    public static TaskAwaiter GetAwaiter(this Action action)
    {
        Task task = new Task(action);
        task.Start();
        return task.GetAwaiter();
    }
}

由于某些原因,这也让我感到担忧,这看起来很像反模式 这不仅仅是创建一个将在单独的线程上同步运行我的动作的任务吗?我也没有看到这通过调用列表。

这些方法中的任何一种都适合异步调用运行委托吗? 有没有一种方法可以使用await语法调用异步委托,同时仍然完全利用异步? 在调用列表中调用具有多个函数的异步委托的正确方法是什么?

1 个答案:

答案 0 :(得分:0)

我认为Eric Lippert的评论比以往任何时候都更能澄清情况。 总的来说,如果您需要对方法的返回类型执行操作,则不应使用多播委托。如果您仍然需要,至少使用Func<Task>签名,则可以使用GetInvocationListas explained here对每个代表进行迭代。

但使用async void方法是否真的无法以多播代理的方式工作?

事实证明,您可以通过使用自定义同步上下文并覆盖async voidOperationStarted方法来通知OperationCompleted方法的开头和结尾。我们还可以覆盖Post方法来设置子操作的同步上下文,以捕获后续的async void次调用。

将它拼凑在一起,你可以得到类似的东西:

class Program
{
    static async Task Main(string[] args)
    {
        Action act = null;
        act += () => { Console.WriteLine("Sync"); };
        act += async () =>
        {
            Callback();

            await Task.Delay(1000);

            Console.WriteLine("Async");
        };

        await AwaitAction(act);

        Console.WriteLine("Done");

        Console.ReadLine();
    }

    static async void Callback()
    {
        await Task.Delay(2000);

        Console.WriteLine("Async2");
    }

    static Task AwaitAction(Action action)
    {
        var delegates = action.GetInvocationList();

        var oldSynchronizationContext = SynchronizationContext.Current;

        var asyncVoidSynchronizationContext = new AsyncVoidSynchronizationContext();

        try
        {
            SynchronizationContext.SetSynchronizationContext(asyncVoidSynchronizationContext);

            var tasks = new Task[delegates.Length];

            for (int i = 0; i < delegates.Length; i++)
            {
                ((Action)delegates[i]).Invoke();
                tasks[i] = asyncVoidSynchronizationContext.GetTaskForLastOperation();
            }

            return Task.WhenAll(tasks);
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext);
        }
    }
}

public class AsyncVoidSynchronizationContext : SynchronizationContext
{
    private TaskCompletionSource<object> _tcs;
    private Task _latestTask;

    private int _operationCount;

    public Task GetTaskForLastOperation()
    {
        if (_latestTask != null)
        {
            var task = _latestTask;
            _latestTask = null;
            return task;
        }

        return Task.CompletedTask;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        Task.Run(() =>
        {
            SynchronizationContext.SetSynchronizationContext(this);

            d(state);
        });
    }

    public override void OperationStarted()
    {
        if (Interlocked.Increment(ref _operationCount) == 1)
        {
            // First operation
            _tcs = new TaskCompletionSource<object>();
            _latestTask = _tcs.Task;
        }

        base.OperationStarted();
    }

    public override void OperationCompleted()
    {
        if (Interlocked.Decrement(ref _operationCount) == 0)
        {
            // Last operation
            _tcs.TrySetResult(null);
        }

        base.OperationCompleted();
    }
}

输出结果为:

  

同步

     

异步

     

Async2

     

完成

当然,此代码仅用于娱乐目的。存在很多限制,例如如果您已经在使用同步它将无法正常工作这一事实上下文(例如WPF之一)。我也确定它在这里和那里都有一些微妙的错误和并发问题。