TaskContinuationOptions

TaskContinuationOptions

我有一些库(套接字网络)代码,它们基于Task提供了基于TaskCompletionSource<T>的API,用于对请求的未决响应。但是,TPL中有一个烦人之处在于,似乎不可能阻止同步继续。我想做的是:

  • 告诉一个TaskCompletionSource<T>,该代码不应允许 call 者附加TaskContinuationOptions.ExecuteSynchronously
  • 使用指定池(而不是
  • )的方式设置结果(SetResult/TrySetResult),以指定应忽略TaskContinuationOptions.ExecuteSynchronously的方式

    具体来说,我的问题是传入的数据正在由专用的读取器处理,并且如果调用者可以附加TaskContinuationOptions.ExecuteSynchronously,则它们可能会使读取器停滞(不仅影响读取器)。以前,我是通过一些黑客来解决此问题的,该黑客可以检测是否存在任何延续,如果存在,则将完成情况推送到ThreadPool上,但是如果调用者饱和了他们的工作队列,这将产生重大影响,因为完成情况不会获得及时处理。如果他们使用的是Task.Wait()(或类似代码),则它们实际上将自身陷入僵局。同样,这就是为什么阅读器使用专用线程而不使用 worker 的原因。

    所以;在尝试拖累TPL团队之前:我是否缺少选择?

    关键点:
  • 我不希望外部 call 者能够劫持我的线程
  • 我不能使用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/

    10-09 21:29