当第三方库使用它时,async void可以解决

Jas*_*der 5 c# async-await

搜索后寻求帮助并未提出好的建议.

我总是避免在代码中使用异步void方法.我不使用事件处理程序.有时,供应商或库无法选择,其方法实现为async void.

如果我的方法本身返回Task,但我别无选择,只能使用async void调用第三方库方法,是否有办法安全地包装他们的方法,以便我可以保持我的代码不受异步void的影响,这里列出的关于终止我的过程?

StackOverflow - 为什么async void bad

我关注的一个例子如下:第三方库方法如下所示

public async void GetSomethingFromAService()
{
    /// their implementation, and somewhere along the way it throws an       exception, in this async void method --- yuck for me
}
Run Code Online (Sandbox Code Playgroud)

我的方法在服务控制器上说:

public async Task<bool> MyMethod()
{
   await ThirdPartyLibrary.GetSomethingFromAService();
   return await Task.FromResult(true);
}
Run Code Online (Sandbox Code Playgroud)

我的方法很好,除了第三方库是异步void并抛出异常.我的应用程序即将死亡.我不希望它,因为我的代码编写得很好而不是异步void.但我无法控制他们的代码.我可以用这种方式将调用包装到异步void方法中,以保护我的代码免于死亡吗?

nos*_*tio 1

这很棘手,并且可能不适用于所有场景,但可以async void通过在自定义同步上下文上开始执行来跟踪方法的生命周期。在这种情况下,SynchronizationContext.OperationStarted/SynchronizationContext.OperationCompleted将在异步 void 方法的开始和结束时相应地被调用。

如果async void方法内部抛出异常,它将被捕获并通过 重新抛出SynchronizationContext.Post。因此,也可以收集所有异常。

下面是一个完整的控制台应用程序示例,说明了这种方法,大致基于Stephen Toub 的AsyncPump(警告:仅进行了轻微测试):

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncVoidTest
{
    class Program
    {
        static async void GetSomethingFromAService()
        {
            await Task.Delay(2000);
            throw new InvalidOperationException(nameof(GetSomethingFromAService));
        }

        static async Task<int> MyMethodAsync()
        {
            // call an ill-designed 3rd party async void method 
            // and await its completion
            var pump = new PumpingContext();
            var startingTask = pump.Run(GetSomethingFromAService);
            await Task.WhenAll(startingTask, pump.CompletionTask);
            return 42;
        }

        static async Task Main(string[] args)
        {
            try
            {
                await MyMethodAsync();
            }
            catch (Exception ex)
            {
                // this will catch the exception thrown from GetSomethingFromAService
                Console.WriteLine(ex);
            }
        }
    }

    /// <summary>
    /// PumpingContext, based on Stephen Toub's AsyncPump
    /// https://blogs.msdn.com/b/pfxteam/archive/2012/02/02/await-synchronizationcontext-and-console-apps-part-3.aspx
    /// /sf/ask/3494498241/
    /// </summary>
    internal class PumpingContext : SynchronizationContext
    {
        private int _pendingOps = 0;

        private readonly BlockingCollection<ValueTuple<SendOrPostCallback, object>> _callbacks =
            new BlockingCollection<ValueTuple<SendOrPostCallback, object>>();

        private readonly List<Exception> _exceptions = new List<Exception>();

        private TaskScheduler TaskScheduler { get; }

        public Task CompletionTask { get; }

        public PumpingContext(CancellationToken token = default(CancellationToken))
        {
            var taskSchedulerTcs = new TaskCompletionSource<TaskScheduler>();

            this.CompletionTask = Task.Run(() =>
            {
                SynchronizationContext.SetSynchronizationContext(this);
                taskSchedulerTcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
                try
                {
                    // run a short-lived callback pumping loop on a pool thread
                    foreach (var callback in _callbacks.GetConsumingEnumerable(token))
                    {
                        try
                        {
                            callback.Item1.Invoke(callback.Item2);
                        }
                        catch (Exception ex)
                        {
                            _exceptions.Add(ex);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _exceptions.Add(ex);
                }
                finally
                {
                    SynchronizationContext.SetSynchronizationContext(null);
                }
                if (_exceptions.Any())
                {
                    throw new AggregateException(_exceptions);
                }
            }, token);

            this.TaskScheduler = taskSchedulerTcs.Task.GetAwaiter().GetResult();
        }

        public Task Run(
            Action voidFunc,
            CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(() =>
            {
                OperationStarted();
                try
                {
                    voidFunc();
                }
                finally
                {
                    OperationCompleted();
                }
            }, token, TaskCreationOptions.None, this.TaskScheduler);
        }

        public Task<TResult> Run<TResult>(
            Func<Task<TResult>> taskFunc,
            CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew<Task<TResult>>(async () =>
            {
                OperationStarted();
                try
                {
                    return await taskFunc();
                }
                finally
                {
                    OperationCompleted();
                }
            }, token, TaskCreationOptions.None, this.TaskScheduler).Unwrap();
        }

        // SynchronizationContext methods
        public override SynchronizationContext CreateCopy()
        {
            return this;
        }

        public override void OperationStarted()
        {
            // called when async void method is invoked 
            Interlocked.Increment(ref _pendingOps);
        }

        public override void OperationCompleted()
        {
            // called when async void method completes 
            if (Interlocked.Decrement(ref _pendingOps) == 0)
            {
                _callbacks.CompleteAdding();
            }
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            _callbacks.Add((d, state));
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotImplementedException(nameof(Send));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)