如何防止任务上的同步延续? [英] How can I prevent synchronous continuations on a Task?
问题描述
我有一些库(套接字网络)代码,它提供了一个基于 Task
的 API,用于基于 TaskCompletionSource
的待处理请求响应.然而,在 TPL 中有一个烦恼,因为它似乎不可能阻止同步延续.我希望能够做的是:
I have some library (socket networking) code that provides a Task
-based API for pending responses to requests, based on TaskCompletionSource<T>
. However, there's an annoyance in the TPL in that it seems to be impossible to prevent synchronous continuations. What I would like to be able to do is either:
- 告诉
TaskCompletionSource
不允许调用者附加TaskContinuationOptions.ExecuteSynchronously
,或 - 以指定应忽略
TaskContinuationOptions.ExecuteSynchronously
的方式设置结果 (SetResult
/TrySetResult
),而是使用池
- tell a
TaskCompletionSource<T>
that is should not allow callers to attach withTaskContinuationOptions.ExecuteSynchronously
, or - set the result (
SetResult
/TrySetResult
) in a way that specifies thatTaskContinuationOptions.ExecuteSynchronously
should be ignored, using the pool instead
具体来说,我遇到的问题是传入的数据正在由专用读取器处理,如果调用方可以使用 TaskContinuationOptions.ExecuteSynchronously
附加,他们可能会拖延读取器(这不仅会影响到他们).以前,我通过一些检测是否存在任何延续的黑客解决了这个问题,如果存在,它将完成推送到ThreadPool
,但是如果调用者已经饱和了他们的工作队列,因为完成不会得到及时处理.如果他们使用 Task.Wait()
(或类似的),那么他们本质上就会死锁.同样,这就是为什么读者在专用线程上而不是使用工作线程.
Specifically, the issue I have is that the incoming data is being processed by a dedicated reader, and if a caller can attach with TaskContinuationOptions.ExecuteSynchronously
they can stall the reader (which affects more than just them). Previously, I have worked around this by some hackery that detects whether any continuations are present, and if they are it pushes the completion onto the ThreadPool
, however this has significant impact if the caller has saturated their work queue, as the completion will not get processed in a timely fashion. If they are using Task.Wait()
(or similar), they will then essentially deadlock themselves. Likewise, this is why the reader is on a dedicated thread rather than using workers.
所以;在我尝试唠叨 TPL 团队之前:我错过了一个选择吗?
So; before I try and nag the TPL team: am I missing an option?
要点:
- 我不希望外部调用者能够劫持我的线程
- 我不能使用
ThreadPool
作为实现,因为它需要在池饱和时工作
- I don't want external callers to be able to hijack my thread
- I can't use the
ThreadPool
as an implementation, as it needs to work when the pool is saturated
以下示例产生输出(顺序可能因时间而异):
The example below produces output (ordering may vary based on timing):
Continuation on: Main thread
Press [return]
Continuation on: Thread pool
问题在于随机调用者设法在主线程"上获得延续.在真正的代码中,这会打断初级阅读器;坏事!
The problem is the fact that a random caller managed to get a continuation on "Main thread". In the real code, this would be interrupting the primary reader; bad things!
代码:
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();
}
}
推荐答案
.NET 4.6 中的新功能:
.NET 4.6 包含一个新的 TaskCreationOptions
:RunContinuationsAsynchronously
.
.NET 4.6 contains a new TaskCreationOptions
: RunContinuationsAsynchronously
.
既然您愿意使用反射来访问私有字段...
Since you're willing to use Reflection to access private fields...
您可以使用 TASK_STATE_THREAD_WAS_ABORTED
标志标记 TCS 的任务,这将导致所有延续不内联.
You can mark the TCS's Task with the TASK_STATE_THREAD_WAS_ABORTED
flag, which would cause all continuations not to be inlined.
const int TASK_STATE_THREAD_WAS_ABORTED = 134217728;
var stateField = typeof(Task).GetField("m_stateFlags", BindingFlags.NonPublic | BindingFlags.Instance);
stateField.SetValue(task, (int) stateField.GetValue(task) | TASK_STATE_THREAD_WAS_ABORTED);
我建议你使用表达式而不是反射发射.这更具可读性,并且具有与 PCL 兼容的优势:
Instead of using Reflection emit, I suggest you use expressions. This is much more readable and has the advantage of being PCL-compatible:
var taskParameter = Expression.Parameter(typeof (Task));
const string stateFlagsFieldName = "m_stateFlags";
var setter =
Expression.Lambda<Action<Task>>(
Expression.Assign(Expression.Field(taskParameter, stateFlagsFieldName),
Expression.Or(Expression.Field(taskParameter, stateFlagsFieldName),
Expression.Constant(TASK_STATE_THREAD_WAS_ABORTED))), taskParameter).Compile();
不使用反射:
如果有人感兴趣,我已经找到了一种无需 Reflection 的方法,但它也有点脏",当然还有不可忽略的性能损失:
If anyone's interested, I've figured out a way to do this without Reflection, but it is a bit "dirty" as well, and of course carries a non-negligible perf penalty:
try
{
Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
source.TrySetResult(123);
Thread.ResetAbort();
}
这篇关于如何防止任务上的同步延续?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!