使用 TaskCompletionSource 与 BufferBlock<T> 包装事件

Wrapping events with TaskCompletionSource vs. BufferBlock<T>

Lucian 在这里讨论了一种模式 (Tip 3: Wrap events up in Task-returning APIs and await them)。

我正在尝试在一个经常调用的方法上实现它,该方法看起来类似于下面的人为代码:

public Task BlackBoxAsync() 
{ 
    var tcs = new TaskCompletionSource<Object>();  // new'ed up every call
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        { 
            DoSomethingStuff(); 
            tcs.SetResult(null); 
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

我担心性能,因为每次调用都会更新 TaskCompletionSource(假设我每 100 毫秒调用一次此方法)。

然后我想改用 BufferBlock<T>,认为它不会在每次调用时都被更新。所以它看起来像:

private readonly BufferBlock<object> signalDone; // dummy class-level variable, new'ed up once in CTOR

public Task BlackBoxAsync() 
{ 

    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        { 
            DoSomethingStuff(); 
            signalDone.Post(null);
        } 
        catch(Exception exc) {  } 
    }); 
    return signalDone.ReceiveAsync(); 
}

调用对象会这样调用它:

for (var i=0; i<10000; i++) {
 await BlackBoxAsync().ConfigureAwait(false);
}

有人对使用 BufferBlock<T> 有任何想法吗?

无论您采用何种解决方案,如果您希望在每次调用此方法时都 await 一个任务,那么创建一个新的 Task 是不可避免的,因为任务不可重用。最简单的方法是使用 TaskCompletionSource.

所以在我看来,第一个选项比使用 BufferBlock 更可取(不出所料,creates a new TaskCompletionSource on ReceiveAsync


更重要的是,您的代码似乎只是将工作卸载到 ThreadPool 和 return 代表该工作的任务。你为什么不使用简单的 Task.Run?

public Task BlackBoxAsync() 
{
    return Task.Run(() => DoSomethingStuff());
}