如何防止任务上的同步延续? [英] How can I prevent synchronous continuations on a Task?

查看:18
本文介绍了如何防止任务上的同步延续?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些库(套接字网络)代码,它提供了一个基于 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();
    }
}

推荐答案

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

这篇关于如何防止任务上的同步延续?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆