2017-04-06 62 views
1

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

Inside runReportTask我创建了一个CancellationTokenSource并使用Task.Factory.StartNew()开始一个新的任务。在任务内部,只要任务没有被取消,就有一个while循环执行处理。根据我的理解,如果cancellationToken.IsCancellationRequested是真的,我的while循环中的其他内容将运行,任务将停止。之后,我期待SubscribeToReport()继续执行 - 这种情况不会发生,并且在StopReportTask()中拨打cancellationTokenSource.Cancel()时,我的代码似乎不会执行任何操作。我究竟做错了什么?我一直在这里呆了一段时间,我似乎无法围绕这应该如何工作。

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

你能否确认在同一个对象上调用'cancellationTokenSource.Cancel()'? – CodingYoshi

+0

包含此代码的类是单例,所以是的。 – Roka545

+0

我开始认为这可能是一个与我正在使用的nanomsg方法有关的问题。我正在使用nanomsg通过发布者/订阅者系统接收数据 - 在我的while循环中,我有一个侦听消息的方法 - 它等待,直到它接收到一个 - 在这段时间内,我不认为while循环继续运行,解释为什么取消令牌不起作用。我已经调试了我的StopReportTask方法,并且取消标记肯定会返回true(因为它要取消该任务)。 – Roka545

回答

5

它包含的await runReportTask从我的理解会刚开始的runReportTask方法,等待它完成执行,然后恢复执行的await之后出现的代码。

代码:

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

它不会永远看起来像我的代码做任何事情,当我打电话cancellationTokenSource.Cancel()在StopReportTask()

这可能是一个不同的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; 
    ... 
} 
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。一定要在最后的陈述中进行清理。

用法:

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,鼓励您在垃圾收集器执行此操作之前尽快释放所需的资源,只要您不再需要它们即可。