Why exactly is void async bad?

Why exactly is void async bad?

回答1

Well, walking through the reasons in the "avoid async void" article:

  • Async void methods have different error-handling semantics. Exceptions escaping from PrimeCustomTask will be very awkward to handle.
  • Async void methods have different composing semantics. This is an argument centered around code maintainability and reuse. Essentially, the logic in PrimeCustomTask is there and that's it - it can't be composed into a higher-level async method.
  • Async void methods are difficult to test. Following naturally from the first two points, it's very difficult to write a unit test covering PrimeCustomTask (or anything that calls it).

It's also important to note that async Task is the natural approach. Of the several languages that have adopted async/await, C#/VB are the only ones AFAIK that support async void at all. F# doesn't, Python doesn't, JavaScript and TypeScript don't. async void is unnatural from a language design perspective.

The reason async void was added to C#/VB was to enable asynchronous event handlers. If you change your code to use async void event handlers:

protected override async void OnLoad(EventArgs e)
{
  if (CustomTask == null)
    await PrimeCustomTask();
}

private async Task PrimeCustomTask()

Then the async void disadvantages are restricted to your event handler. In particular, exceptions from PrimeCustomTask are propagated naturally to its (asynchronous) callers (OnLoad), PrimeCustomTask can be composed (called naturally from other asynchronous methods), and PrimeCustomTask is much easier to include in a unit test.

 

回答2

Using void async is only generally seen as "bad" because:

  • You can’t wait for its completion (as mentioned in this post already
  • Is especially painful if it is called by a parent thread that exits before it has completed
  • Any unhandled exceptions will terminate your process (ouch!)

There are plenty of cases (like yours) where using it is fine. Just be cautious when using it.

 

System.ObjectDisposedException: 'Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope

Your problem is here:

 public async void Approvefiles(string[] data)

async void methods pretty much means that nothing will wait for that method to finish before continuing (as well as causing a host of other problems).

So I imagine your request scope is getting cleaned up before your second _mediator.Send call, meaning nothing can be resolved.

You need to change the signature to:

public async Task Approvefiles(string[] data)

Then await that method as needed in your controller to make sure it completes before your request ends.

There's an answer here about why async void is bad, for more detail.

 

为什么不要使用 async void?

问题

在使用 Abp 框架的后台作业时,当后台作业抛出异常,会导致整个程序崩溃。在 Abp 框架的底层执行后台作业的时候,有 try/catch 语句块用来捕获后台任务执行时的异常,但是在这里没有生效。

原始代码如下:

public class TestAppService : ITestAppService
{
	private readonly IBackgroundJobManager _backgroundJobManager;

	public TestAppService(IBackgroundJobManager backgroundJobManager)
	{
		_backgroundJobManager = backgroundJobManager;
	}

	public Task GetInvalidOperationException()
	{
		throw new InvalidOperationException("模拟无效操作异常。");
	}

	public async Task<string> EnqueueJob()
	{
		await _backgroundJobManager.EnqueueAsync<BG, string>("测试文本。");

		return "执行完成。";
	}
}

public class BG : BackgroundJob<string>, ITransientDependency
{
	private readonly TestAppService _testAppService;

	public BG(TestAppService testAppService)
	{
		_testAppService = testAppService;
	}

	public override async void Execute(string args)
	{
		await _testAppService.GetInvalidOperationException();
	}
}

调用接口时的效果:

原因

出现这种情况是因为任何异步方法返回 void 时,抛出的异常都会在 async void 方法启动时,处于激活状态的同步上下文 (SynchronizationContext) 触发,我们的所有 Task 都是放在线程池执行的。

所以在上述样例当中,此时 AsyncVoidMethodBuilder.Create() 使用的同步上下文为 null ,这个时候 ThreadPool 就不会捕获异常给原有线程处理,而是直接抛出。

线程池在底层使用 AsyncVoidMethodBuilder.Craete() 所拿到的同步上下文,所捕获异常的代码如下:

internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
{
    var edi = ExceptionDispatchInfo.Capture(exception);

    // 同步上下文是空的,则不会做处理。
    if (targetContext != null)
    {
        try
        {
            targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
            return;
        }
        catch (Exception postException)
        {
            edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
        }
    }
}

虽然你可以通过挂载 AppDoamin.Current.UnhandledException 来监听异常,不过你是没办法从异常状态恢复的。

参考文章:

Stephen Cleary: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Jerome Laban's:https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

布鲁克石:https://www.cnblogs.com/brookshi/p/5240510.html

解决

可以使用 AsyncBackgroundJob<TArgs> 替换掉之前的 BackgroundJob<TArgs> ,只需要实现它的 Task ExecuteAsync(TArgs args) 方法即可。

public class BGAsync : AsyncBackgroundJob<string>,ITransientDependency
{
	private readonly TestAppService _testAppService;

	public BGAsync(TestAppService testAppService)
	{
		_testAppService = testAppService;
	}

	protected override async Task ExecuteAsync(string args)
	{
		await _testAppService.GetInvalidOperationException();
	}
}

 

 

 

 

 

 

posted @ 2021-10-20 10:32  ChuckLu  阅读(90)  评论(0编辑  收藏  举报