使用TaskCompletionSource和BufferBlock<T>包装事件的区别

4
这里讨论了Lucian提出的一种模式(Tip 3:将事件包装在返回任务的API中并等待它们)。
我正在尝试将其应用于一个经常调用的方法,该方法看起来像下面虚构的代码:
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>的想法?

1
你是否了解响应式扩展? - Johan Larsson
这个是“新的”并且比TAP更好吗? - alpinescrambler
Rx比TAP早,但在4.5推出时更新为使用TAP。它是一个框架,而不是一个单独的系统(有点像Entity Framework vs SqlCommand)。 - Scott Chamberlain
1个回答

6
无论您选择哪种解决方案,如果您想在每次调用此方法时使用await等待任务,则创建新的Task是不可避免的,因为任务不可重复使用。最简单的方法是使用TaskCompletionSource
因此,我认为第一种选项比使用BufferBlock更可取(不出所料,在ReceiveAsync上创建新的TaskCompletionSource)。
更重要的是,您的代码似乎只是将工作转移到线程池并返回表示该工作的任务。为什么不使用简单的Task.Run呢?
public Task BlackBoxAsync() 
{
    return Task.Run(() => DoSomethingStuff());
}

感谢您的输入。这种方法是一种人为的方法,只是为了说明情况。我的真正用途实际上是由TaskCompletionSource包装的事件。 - alpinescrambler
@alpinescrambler 明白了。所以忽略第二部分。 - i3arnon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接