我有一些库(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相关问答推荐

Docker失败文件找不到

ZstdNet库的问题:Src大小不正确,异常

如何使用AWS Lambda函数制作网络挂钩?

使用React路由加载器获取数据不能正常工作

.NET最小API BadRequest响应不返回正文

使用CLR将数据从Excel导入SQL Server时出错

如何从 tshark 的 stderr 捕获实时数据包计数?

.NET Core 中的微服务

通过 System.Net.Mail 的 VB SMTP 停止工作

如何在选项卡中 Select Winforms NumericUpDown 中的所有文本?

如何在任务栏顶部全屏显示 Windows 窗体?

ASP.NET Core 等效于 ASP.NET MVC 5 的 HttpException

覆盖方法上的 C# 可选参数

如何在 C# 中安全地将 System.Object 转换为bool?

如何将 System.Type 转换为其可为空的版本?

Guid.Parse() 或 new Guid() - 有什么区别?

忽略 LINQ to XML 中的命名空间

在 C# 中使用 Bitmap 对象查找图像格式

Windows 服务在哪个目录中运行?

如何卸载Microsoft .NET Core 1.0.0 RC2 - VS 2015 Tooling Preview 1?