【问题标题】:TaskScheduler with queue and timeout issue具有队列和超时问题的 TaskScheduler
【发布时间】:2016-03-07 15:52:37
【问题描述】:

我正在尝试实现的内容:

将任务排入队列并并行运行指定数量的任务调度程序,而其他人则在队列中等待开始。每个任务都有超时,当任务运行时开始计数,如果超过该时间,任务将被取消并抛出 TimeoutException,该异常在 ContinueWith 中处理(或某些紧随其后运行的任务)。用户应该可以取消任务。

我得到了什么:

当第一个任务失败时,所有其他任务都会立即失败。

这是我的任务计划程序的完整代码(取自 MSDN,经过一些修改):

http://pastebin.com/KSMbDTH5。 (有问题的函数在第 161 行)

这里是示例用法:

var taskTokens = new List<CancellationToken>();
var factory = new TaskScheduleFactory(new ParallelOptions() { MaxDegreeOfParallelism = 1 }); //for the purpose of testing, supposed to work and with higher values
for (var i = 0; i < 10; i++)
{
    //TaskScheduleFactory.cs, line 161
    var taskToken = factory.Add(
        (token) => //Task
        {
            Console.WriteLine("Start");
            Thread.Sleep(5000);
            if (token.IsCancellationRequested) return; //Cancelled by timeout
            Console.WriteLine("This should not print");
        },
        (task) => //ContinueWith
        {
            if (task.IsFaulted)
            {
                Console.WriteLine("Fail");
            }
            else if (!task.IsCompleted)
            {
                Console.WriteLine("Not completed");
            }
            else Console.WriteLine("Done");
        },
        2000 //Timeout
    );
    taskTokens.Add(taskToken);
}

它应该如何工作:(我们在 2 秒后强制超时事件,因此两个任务都不会完成)

对于 MaxDegreeOfParallelism = 1:

Start;
(Wait 2sec)
Fail;
Start;
(Wait 2sec)
Fail;
....

对于 MaxDegreeOfParallelism = 2:

Start;
Start;
(Wait 2sec)
Fail;
Fail;
Start;
Start;
(Wait 2sec)
Fail;
Fail;
....

它是如何工作的:

Start;
(Wait 2sec)
Fail;
Fail;
Fail;
Fail;
...

(对于 MaxDegreeOfParallelism = 1,其余的也是一团糟)

注意:这是我使用 TPL 的第一步,请原谅我的愚蠢

【问题讨论】:

    标签: c# multithreading task-parallel-library taskscheduler


    【解决方案1】:

    Add 中,您调用.TimeoutAfter(timeoutInMilliseconds, cts)。这意味着超时会在您创建任务后立即开始。我认为您的意图是仅在任务开始执行时才开始超时。

    由于第一个任务比超时时间长,所有其他任务在轮到它们时已经超时。

    【讨论】:

    • 好点。我将 TimeoutAfter 和 ContinueWith 移到动作的主体中,但仍然难以使其正常工作。您能否详细说明如何解决它?
    • 你在那里写了一些非常高级的代码。我认为你可以解决这个问题:) 类似的东西应该可以工作:var workTask = Instance.StartNew(() =&gt; { cts.CancelAfter(timeoutInMilliseconds); action(cts.Token); }, cts.Token); var resultTask = Task.WhenAll(workTask, cts.Token);。这可能会在您不喜欢的状态下完成resultTask(我认为它最终会被取消)。如果您想要更多控制权,您可以使用TaskCompletionSource 实现任何完成行为。
    • 呵呵,我前面提到我从MSDN示例中复制了大部分代码。我写的唯一部分是实际上不起作用的部分。谢谢您的帮助!我将离开几天,因此需要一些时间来验证它并将其标记为答案(除非我的愚蠢阻止​​我正确实施它)。干杯,祝您度过愉快的一周!
    • 有点不对劲。对于初学者,Task.WhenAll 不接受 CancellationTokesn 作为第二个参数。而且我看不到ContinueWith。对不起,你这么牛逼……
    • 好吧,我不是 100% 熟悉 TPL 的那个领域。这看起来不错:stackoverflow.com/a/27240225/122718
    【解决方案2】:

    不是完美的解决方案,而是我能想到的最好的解决方案:

    public CancellationTokenSource Add(Action<CancellationToken> action, Action<Task> callback, int timeoutInMilliseconds)
    {
        var cts = new CancellationTokenSource();
        Instance.StartNew(() =>
        {
            cts.CancelAfter(timeoutInMilliseconds);
            var task = Task.Factory.StartNew(() => action(cts.Token), cts.Token, TaskCreationOptions.AttachedToParent|TaskCreationOptions.LongRunning, TaskScheduler.Default);
            try
            {
                task.Wait(timeoutInMilliseconds, cts.Token);
            }
            catch (OperationCanceledException) { }
            callback(task);
        }, cts.Token);
        return cts;
    }
    

    简而言之:当任务从队列中启动时,它会在默认任务调度器上创建子任务(因为另一个只会将它入队),然后我们等待一定的时间并调用回调函数。不知道这有多糟糕。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-30
      • 2015-10-21
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-31
      • 1970-01-01
      相关资源
      最近更新 更多