我有一些库(socket networking)代码,为请求的待处理响应提供了基于TaskCompletionSource<T>
的基于任务的API。然而,在TPL中存在一个烦恼,即似乎无法防止同步延续。我希望能够:
- 告诉
TaskCompletionSource<T>
不允许调用者附加TaskContinuationOptions.ExecuteSynchronously
选项,或 - 以一种指定
TaskContinuationOptions.ExecuteSynchronously
应被忽略并使用线程池的方式设置结果(SetResult
/TrySetResult
)
具体来说,我面临的问题是,传入的数据正在由专用阅读器处理,如果调用者可以附加TaskContinuationOptions.ExecuteSynchronously
选项,它们可以阻止阅读器(这会影响更多的操作)。之前,我通过一些技巧解决了这个问题,检测是否存在任何继续操作,如果存在,则将完成推送到ThreadPool
上,但是如果调用者已经饱和其工作队列,则会对其产生重大影响,因为完成操作将无法及时得到处理。如果他们使用Task.Wait()
(或类似),他们将会陷入死锁。同样,这就是为什么阅读器在专用线程上而不是使用工作者的原因。
所以,在我尝试催促TPL团队之前:
- 我不希望外部调用者能够劫持我的线程
- 我不能将
ThreadPool
作为实现,因为它需要在池饱和时工作
以下示例会生成输出(顺序可能因时间而异):
Continuation on: Main thread
Press [return]
Continuation on: Thread pool
问题在于一个随机的呼叫者成功地获得了“主线程”的延续。在实际代码中,这将会打断主要的读取器;糟糕的事情发生了!
代码:
using System;
using System.Threading;
using System.Threading.Tasks;
static class Program
{
static void Identify()
{
var thread = Thread.CurrentThread;
string name = thread.IsThreadPoolThread
? "Thread pool" : thread.Name;
if (string.IsNullOrEmpty(name))
name = "#" + thread.ManagedThreadId;
Console.WriteLine("Continuation on: " + name);
}
static void Main()
{
Thread.CurrentThread.Name = "Main thread";
var source = new TaskCompletionSource<int>();
var task = source.Task;
task.ContinueWith(delegate {
Identify();
});
task.ContinueWith(delegate {
Identify();
}, TaskContinuationOptions.ExecuteSynchronously);
source.TrySetResult(123);
Console.WriteLine("Press [return]");
Console.ReadLine();
}
}
TaskCompletionSource
,以防止直接调用ContinueWith
,因为TaskCompletionSource
和Task
都不适合从它们继承。 - DennisThreadPool
来做这件事(我已经提到过了 - 它会引起问题),要么你有一个专门的“待处理继续”线程,然后它们(指定了ExecuteSynchronously
的继续)可以劫持那个线程 - 这会导致完全相同的问题,因为它意味着其他消息的继续可能会被阻塞,这又会影响多个调用者。 - Marc GravellTaskCompletionSource<T>
不允许您指定调度程序,因为它从未在调度程序上运行;附加任务可以在ContinueWith
中指定调度程序,但如果我们知道所有附加任务都会正确处理事情,那么问题就没有意义了。 - Marc Gravell