• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

防止任务上的同步延续?

用户头像
it1352
帮助1

问题说明

我有一些库(套接字网络)代码,它提供了一个基于 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 with TaskContinuationOptions.ExecuteSynchronously, or
  • set the result (SetResult / TrySetResult) in a way that specifies that TaskContinuationOptions.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();
    }
}

正确答案

#1

.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();
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /reply/detail/tanhcgkgke
系列文章
更多 icon
同类精品
更多 icon
继续加载