我有一些库(socket networking)代码,它提供了一个基于Task的API,用于基于TaskCompletionSource<T>的未决请求响应.然而,TPL中有一个令人烦恼的地方,那就是似乎不可能阻止同步的继续.我能做的是:

  • 告诉电话号码为TaskCompletionSource<T>的人不要让打电话的人连接TaskContinuationOptions.ExecuteSynchronously,或者
  • 将结果(SetResult/TrySetResult)设置为指定应该忽略TaskContinuationOptions.ExecuteSynchronously,而是使用池

具体地说,我遇到的问题是,传入的数据是由专用读取器处理的,如果呼叫者可以连接到TaskContinuationOptions.ExecuteSynchronously,他们就可以拖延读取器(这影响的不仅仅是他们).以前,我曾通过一些黑客攻击解决过这个问题,它检测是否存在any个延续,如果存在,它会将完成推到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();
    }
}

推荐答案

New in .NET 4.6:

.NET4.6包含一个新的TaskCreationOptions:RunContinuationsAsynchronously.


既然你愿意用反射来访问私有领域...

您可以使用TASK_STATE_THREAD_WAS_ABORTED标志标记TCS的任务,这将导致所有continuation都不内联.

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);

Edit:

我建议您使用表达式,而不是使用反射emits .这更具可读性,并且具有与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();

Without using Reflection:

如果有人感兴趣的话,我已经想出了一种不用思考就能做到这一点的方法,但它也有点"脏",当然会带来不可忽视的性能损失:

try
{
    Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
    source.TrySetResult(123);
    Thread.ResetAbort();
}

.net相关问答推荐

使用托管身份而不是检测密钥配置Application Insights

信号量的多线程问题

既然 .NET 有一个垃圾收集器,为什么我们需要终结器/析构器/dispose-pattern?

您是否使用 TestInitialize 或测试类构造函数来准备每个测试?为什么?

在 Moq 中模拟泛型方法而不指定 T

Silverlight 与 Flex

如何以编程方式 Select ListView 中的项目?

将双精度转换为带有 N 个小数的字符串,点作为小数分隔符,并且没有千位分隔符

无法将文件 *.mdf 作为数据库附加

String.Format - 它是如何工作的以及如何实现自定义格式字符串

如何允许程序集(单元测试)访问另一个程序集的内部属性?

一个接口是否应该继承另一个接口

从 C# 中的字符串中删除最后一个字符.优雅的方式?

错误 NU1605 检测到包降级

将月份 int 转换为月份名称

如何从头开始以编程方式配置 log4net(无配置)

表单不响应 KeyDown 事件

通过继承扩展枚举

有没有一种简单的方法来判断 .NET Framework 版本?

MVVM 没有意义吗?