如何正确启动和取消任务。

时间:2017-04-06 03:47:37

标签: c# multithreading asynchronous async-await task

我无法理解如何正确启动任务并取消任务。我已经更改了我的代码,以便更容易理解我的问题并将其粘贴在下面(我还添加了一些评论)。首先调用SubscribeToReport方法 - 它包含一个等待runReportTask,根据我的理解,它将启动runReportTask方法,等待它完成执行,然后继续执行之后出现的代码await

runReportTask内我创建了一个CancellationTokenSource并使用Task.Factory.StartNew()开始一项新任务。在任务内部,只要任务没有被取消,就会有一个while循环执行处理。根据我的理解,如果cancellationToken.IsCancellationRequested为真,我的while循环中的else将运行,任务将停止。在那之后,我希望SubscribeToReport()继续执行 - 这不会发生,而且当我在cancellationTokenSource.Cancel()中调用StopReportTask()时,我的代码似乎没有做任何事情。我究竟做错了什么?我已经有一段时间了,我似乎无法理解这应该如何运作。

private async static void SubscribeToReport()
{
    Console.WriteLine("Waiting for task to finish or cancel...");

    await runReportTask();

    Console.WriteLine("Task has finished or was canceled."); // THIS LINE NEVER EXECUTES
}

private static CancellationTokenSource cancellationTokenSource;
private static CancellationToken cancellationToken;
private bool moreToDo = true;
private static Task runReportTask()
{
    cancellationTokenSource = new CancellationTokenSource();
    cancellationToken = cancellationTokenSource.Token;

    return Task.Factory.StartNew(() =>
    {
        // Were we already canceled?
        cancellationToken.ThrowIfCancellationRequested();

        while(moreToDo == true)
        {
            if(!cancellationToken.IsCancellationRequested)
            {
                // Do important code here
            }
            else{
                Console.WriteLine("Task canceled."); // THIS LINE NEVER EXECUTES WHEN CALLING StopReportTask() below
                cancellationToken.ThrowIfCancellationRequested();
                break;
            }
        }

    }, cancellationToken);
}

// This method is called from another source file
private void StopReportTask()
{
    Console.WriteLine("Stopping report task...");

    cancellationTokenSource.Cancel();
}

2 个答案:

答案 0 :(得分:5)

  

它包含一个等待runReportTask,根据我的理解,它将启动runReportTask方法,等待它完成执行,然后继续执行等待之后出现的代码。

代码:

await runReportTask();

就像这段代码:

var task = runReportTask();
await task;

因此,要清楚,正是方法调用启动任务运行。 await只是(异步)等待它完成。

同样重要的是要注意await异步等待。这意味着它暂停方法的执行并返回。因此,就您的调用代码所知,SubscribeToReport已完成执行。

(这是problems of async void中的一个;它不易消耗 - 调用代码不知道它何时完成。)

  

在runReportTask内部我创建一个CancellationTokenSource并使用Task.Factory.StartNew()开始一个新任务。

作为旁注,您应该使用Task.RunTask.Factory.StartNew is a low-level API with dangerous default settings

  

当我在StopReportTask()中调用cancellationTokenSource.Cancel()时,我的代码似乎没有做任何事情

这可能是一个不同的cancellationTokenSource实例。

你可以尝试这个,作为一个测试 - 我不确定它是否是你想要的语义,但它会导致runReportTask取消之前的尝试):

private static CancellationTokenSource cancellationTokenSource;
private static readonly object _mutex = new object();
private bool moreToDo = true;
private static Task runReportTask()
{
  CancellationTokenSource oldCts, currentCts;
  lock (_mutex)
  {
    oldCts = cancellationTokenSource;
    currentCts = cancellationTokenSource = new CancellationTokenSource();
  }
  if (oldCts != null)
    oldCts.Cancel();
  var cancellationToken = currentCts.Token;
  ...
}

答案 1 :(得分:2)

可能是输入错误,但moreToDo不是静态的。您的静态函数RunReportTask无法访问它。此外,您似乎希望runReportTask之外的其他人可以更改moreToDo来停止此任务。你确定要这个吗?

如果您希望其他人停止您的任务,为什么不让他们使用CancellationTokenSource?

另一方面,如果您只希望RunReportTask是唯一更改moreToDo的人,那么为什么要让其他人访问?

话虽如此,我真的不确定让你的程序保持静态是否明智。你真的打算让某人开始你的过程并让其他人停止这个过程吗?

另一个问题是,如果多个初学者开始执行任务,CancellationTokenSource将替换为新任务,而已经运行的任务仍会检查旧CancellationTokenSource创建的令牌,并且没有人可以阻止旧的运行任务。你确定这是你的意图吗?

如果您强制任务启动器提供CancellationTokenSource,则不会出现这些问题,这更像是MSDN在文章Cancellation in managed threads中提出的建议。

如果您让任务的启动者创建CancellationTokenSource,您将拥有正在运行的任务的所有者,该任务可以决定是否应该共享所有权。未经任务所有者同意,其他人无法阻止此任务。

因为您的SubscribeToReport返回void而不是Task我认为它是事件处理程序的简化版本。如果没有,我建议你让它返回任务。

SubscribeToReport的非静态版本如下:

public async Task SubscribeToReport(CancellationToken token)
{
    Console.WriteLine("Waiting for task to finish or cancel...");
    await Task.Run(runReportTask(token), token);
    Console.WriteLine("Task has finished or was canceled.");
}

private Task runReportTask(CancellationToken token)
{
    bool moreToDo = false;
    token.ThrowIfCancellationRequested();
    while(moreToDo && !Toke.IsCancellatinRequested)
    {
        // Do important code here, 
        // if this takes some time, make sure that 
        // you reqularly check IsCancellationRequested
        // this part runs until!moreToDo
     }

     if (token.IsCancellationRequested)
     {
         Console.WriteLine("Task canceled."); 
         // do cleanup here
     }
}

作为替代方案:不要检查token.IsCancellationRequested,而是调用token.ThrowIfCancellationRequested。一定要在finally语句中进行清理。

用法:

using (var cancellationTokenSource = new CancellationTokenSource())
{
    var token = CancellationTokenSource.Token;
    var task = SubscribeToReport(token);
    // here you are free to do other things,
    // if you decide to cancel:
    cancellationTokenSource.Cancel();
    // or:
    cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));

    // if you have nothing else to do but wait for the task to be completed
    // or cancelled:
    await task;
    ProcessTaskResult(task);
}        

请注意,任务完成后,CancellationTokenSource会被整齐地处理掉。 CancellationTokenSource的设计者实现了IDisposable,以鼓励您在垃圾收集器执行此操作之前不再需要它们时释放所需的资源。