C# 清理类 Dispose() 中的后台任务,而不会有死锁的风险

问题描述 投票:0回答:1

有时我会有一个类在后台执行一些异步操作,通常是网络或其他 IO 操作。我希望它在调用

Dispose()
时停止并清理,但如果以某种方式从后台任务本身调用
Dispose()
,这种模式似乎可能会导致死锁(我怀疑普通线程可能会出现类似的缺陷,如好吧)。

    某种形式的
  • TaskCompletionSource.SetResult
    等可能会导致某些外部
    await
    继续在运行后台任务的线程上运行,然后调用dispose。
  • 有时后台任务会调用回调/事件,最终以某种方式在返回之前调用 Dispose。
是否有安全运行后台操作(例如网络客户端)的标准模式?

    我是否应该避免
  • Dispose()
     函数等待后台内容完成并使用其他方法来确保一切在从 main 返回之前停止?
  • 是否总是调用
  • Dispose()
    的代码的错误,让它从“回调”中发生(对于传统线程,似乎许多库都会这么说,但
    await foo
    潜在的上下文切换使线程更难控制)? 
  • 我是否应该避免后台任务在自己的线程上调用回调(事件/
  • Action
    /
    Func
    Subject<T>.OnNext()
    TaskCompletionSource.SetResult()
    等)?
非常简单的示例,可能会中断,通常

BackgroundTask

 类似于 WebSocket 或其他网络服务,会一直挂起,直到应用程序关闭。

public sealed class BackgroundTask : IDisposable { private readonly CancellationTokenSource _cts = new(); private readonly Task _task; // Probably some sort of message queue private TaskCompletionSource? _thingToDoTcs; public BackgroundTask() { _task = TaskMainAsync(_cts.Token); } public void Dispose() { _cts.Cancel(); _task.Wait(); } public Task DoSomethingOnTask() { _thingToDoTcs = new(); return _thingToDoTcs.Task; } private async Task TaskMainAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { await Task.Delay(100); if (_thingToDoTcs != null) { Console.WriteLine("Doing a thing..."); await Task.Delay(1000); _thingToDoTcs.SetResult(); } // Doing other stuff as well, e.g. a Socket read loop } } static async Task AsyncMain() { using var background = new BackgroundTask(); var task = background.DoSomethingOnTask(); Console.WriteLine("Doing other stuff..."); await task; // May continue on a different thread to the caller Console.WriteLine("Done everything, time to shut down."); // Can deadlock because `await task` ends up being the thread running TaskMainAsync // and then Dispose() calls .Wait() on it. // `using` blocks make implicit on return, exception, etc. so can't fully control call site background.Dispose(); Console.WriteLine("Disposed"); } static void Main(string[] args) { AsyncMain().Wait(); Console.WriteLine("Exit main"); } }
    
c# .net async-await .net-4.8
1个回答
1
投票
您可以使用

IAsyncDisposable

 来代替,进行等待处理。请注意使用 
await using
 而不是普通的 
using

public sealed class BackgroundTask : IAsyncDisposable { ...... public ValueTask Dispose() { _cts.Cancel(); await _task; } ...... static async Task AsyncMain() { await using var background = new BackgroundTask(); var task = background.DoSomethingOnTask(); Console.WriteLine("Doing other stuff..."); await task; // May continue on a different thread to the caller Console.WriteLine("Done everything, time to shut down."); Console.WriteLine("Disposed"); }
    
© www.soinside.com 2019 - 2024. All rights reserved.