我有一些库(套接字网络)代码,它们基于Task
提供了基于TaskCompletionSource<T>
的API,用于对请求的未决响应。但是,TPL中有一个烦人之处在于,似乎不可能阻止同步继续。我想做的是:
TaskCompletionSource<T>
,该代码不应允许 call 者附加TaskContinuationOptions.ExecuteSynchronously
或SetResult
/TrySetResult
),以指定应忽略TaskContinuationOptions.ExecuteSynchronously
的方式具体来说,我的问题是传入的数据正在由专用的读取器处理,并且如果调用者可以附加
TaskContinuationOptions.ExecuteSynchronously
,则它们可能会使读取器停滞(不仅影响读取器)。以前,我是通过一些黑客来解决此问题的,该黑客可以检测是否存在任何延续,如果存在,则将完成情况推送到ThreadPool
上,但是如果调用者饱和了他们的工作队列,这将产生重大影响,因为完成情况不会获得及时处理。如果他们使用的是Task.Wait()
(或类似代码),则它们实际上将自身陷入僵局。同样,这就是为什么阅读器使用专用线程而不使用 worker 的原因。所以;在尝试拖累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();
}
}
最佳答案
.NET 4.6中的新功能:
.NET 4.6包含一个新的TaskCreationOptions
:RunContinuationsAsynchronously
。
由于您愿意使用Reflection访问私有(private)字段...
您可以使用TASK_STATE_THREAD_WAS_ABORTED
标志标记TCS的“任务”,这将导致所有连续都无法内联。
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兼容的优点:
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();
,不使用反射:
如果有人感兴趣,我想出了一种在没有反射的情况下执行此操作的方法,但这也有点“肮脏”,并且当然会受到不容忽视的性能损失:
try
{
Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
source.TrySetResult(123);
Thread.ResetAbort();
}
关于c# - 如何防止任务上的同步继续?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22579206/