Mono:同步延续任务的问题

本文关键字:问题 任务 延续 同步 Mono | 更新日期: 2023-09-27 18:22:22

在实现的自定义TaskScheduler上运行任务时,偶尔会得到以下InvalidOperationException

在调查这个问题后,它似乎是Mono实现中的一个bug。当一个连续任务被标记为TaskContinuationOptions.ExecuteSynchronously时,它将被传递给任务调度器的TryExecuteTaskInline方法;然而,如果后者拒绝执行它并返回false,那么将不可避免地遇到以下异常(根据下面的代码摘录)。

有人能在Mono上提出解决这个问题的方法吗?我正在考虑更改我的TryExecuteTaskInline实现,使其始终接受执行同步延续;然而,我还没有找到一种方法来确定任务是否是的延续(不使用反射)。

System.InvalidOperationException: Start may not be called on a continuation task
  at System.Threading.Tasks.Task.Start (System.Threading.Tasks.TaskScheduler scheduler) [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Task.RunSynchronouslyCore (System.Threading.Tasks.TaskScheduler scheduler) [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.TaskContinuation.Execute () [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Task.ProcessCompleteDelegates () [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Task.Finish () [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Task.ThreadStart () [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.TaskScheduler.TryExecuteTask (System.Threading.Tasks.Task task) [0x00000] in <filename unknown>:0 
  at System.Threading.Tasks.Schedulers.WorkStealingTaskScheduler.DispatchLoop (CancellationToken cancellationToken) [0x00000] in <filename unknown>:0 

我正在相关(精简)部分下面粘贴。。。

…来自TaskContinuation.cs:

class TaskContinuation
{
    public void Execute ()
    {
        // ...
        if ((continuationOptions & TaskContinuationOptions.ExecuteSynchronously) != 0)
            task.RunSynchronouslyCore (task.scheduler);
        else
            task.Schedule ();
    }
}

…来自Task.cs:

public class Task
{
    internal void RunSynchronouslyCore(TaskScheduler scheduler)
    {
        // ...
        if (scheduler.RunInline(this, false))
            return;
        Start(scheduler);
        Wait();
    }
    public void Start(TaskScheduler scheduler)
    {
        // ...
        if (IsContinuation)
            throw new InvalidOperationException("Start may not be called on a continuation task");
        SetupScheduler(scheduler);
        Schedule();
    }
}

…和来自TaskScheduler.cs:

public abstract class TaskScheduler
{
    protected abstract bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued);
    internal bool RunInline(Task task, bool taskWasPreviouslyQueued)
    {
        // ...
        return TryExecuteTaskInline(task, taskWasPreviouslyQueued);
    }
}

Mono:同步延续任务的问题

修复起来很简单。唯一需要的改变就是改变https://github.com/mono/mono/blob/master/mcs/class/corlib/System.Threading.Tasks/Task.cs#L228调用方法Schedule而不是Start

对于任何遇到此问题但被限制使用未修复版本的Mono(高达并包括Mono 3.0.12)的人,您可以使用以下破解方法。它的效率很低,因为它需要读取堆栈跟踪,所以在大多数情况下尽量避免检查。

public class MyTaskScheduler : TaskScheduler
{
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        if (this.CanInlineTasks() ||    // should return true most of the time
            IsSynchronousContinuationTask(task))
            return base.TryExecuteTaskInline(task, taskWasPreviouslyQueued);
        return false; 
    }
    private static bool IsSynchronousContinuationTask(Task task)
    {
        // assuming Mono
        string stackTrace = Environment.StackTrace;
        return stackTrace.Contains("System.Threading.Tasks.Task.RunSynchronouslyCore") 
            && stackTrace.Contains("System.Threading.Tasks.TaskContinuation.Execute");
    }
}