C# 如何取消等待中的任务?

C# 如何取消等待中的任务?,c#,task-parallel-library,.net-4.5,C#,Task Parallel Library,.net 4.5,我正在玩这些Windows8WinRT任务,我正在尝试使用下面的方法取消一个任务,它在某种程度上起作用。CancelNotification方法确实会被调用,这会使您认为任务已被取消,但在后台,任务会继续运行,然后在任务完成后,任务的状态始终为“已完成”且从未取消。有没有办法在任务取消时完全停止 private async void TryTask() { CancellationTokenSource source = new CancellationTokenSource();

我正在玩这些Windows8WinRT任务,我正在尝试使用下面的方法取消一个任务,它在某种程度上起作用。CancelNotification方法确实会被调用,这会使您认为任务已被取消,但在后台,任务会继续运行,然后在任务完成后,任务的状态始终为“已完成”且从未取消。有没有办法在任务取消时完全停止

private async void TryTask()
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.Token.Register(CancelNotification);
    source.CancelAfter(TimeSpan.FromSeconds(1));
    var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);

    await task;            

    if (task.IsCompleted)
    {
        MessageDialog md = new MessageDialog(task.Result.ToString());
        await md.ShowAsync();
    }
    else
    {
        MessageDialog md = new MessageDialog("Uncompleted");
        await md.ShowAsync();
    }
}

private int slowFunc(int a, int b)
{
    string someString = string.Empty;
    for (int i = 0; i < 200000; i++)
    {
        someString += "a";
    }

    return a + b;
}

private void CancelNotification()
{
}
private async void TryTask()
{
CancellationTokenSource=新的CancellationTokenSource();
source.Token.Register(取消通知);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task=task.Factory.StartNew(()=>slowFunc(1,2),source.Token);
等待任务;
如果(任务已完成)
{
MessageDialog md=newmessagedialog(task.Result.ToString());
等待md.ShowAsync();
}
其他的
{
MessageDialog md=新建MessageDialog(“未完成”);
等待md.ShowAsync();
}
}
专用内部slowFunc(内部a、内部b)
{
string someString=string.Empty;
对于(int i=0;i<200000;i++)
{
someString+=“a”;
}
返回a+b;
}
私有作废取消通知()
{
}
请阅读(它是在.NET 4.0中引入的,从那时起基本上没有变化)和,它提供了有关如何将
取消令牌
异步
方法结合使用的指南

总之,将
CancellationToken
传递到每个支持取消的方法中,该方法必须定期检查

private async Task TryTask()
{
  CancellationTokenSource source = new CancellationTokenSource();
  source.CancelAfter(TimeSpan.FromSeconds(1));
  Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);

  // (A canceled task will raise an exception when awaited).
  await task;
}

private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
  string someString = string.Empty;
  for (int i = 0; i < 200000; i++)
  {
    someString += "a";
    if (i % 1000 == 0)
      cancellationToken.ThrowIfCancellationRequested();
  }

  return a + b;
}
专用异步任务TryTask()
{
CancellationTokenSource=新的CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task Task=Task.Run(()=>slowFunc(1,2,source.Token),source.Token);
//(已取消的任务在等待时将引发异常)。
等待任务;
}
专用int slowFunc(int a、int b、CancellationToken CancellationToken)
{
string someString=string.Empty;
对于(int i=0;i<200000;i++)
{
someString+=“a”;
如果(i%1000==0)
cancellationToken.ThrowIfCancellationRequested();
}
返回a+b;
}

我只想在已经被接受的答案中添加一点。我被困在这件事上,但我在处理整个事件上走的是另一条路。我没有运行wait,而是向任务添加一个已完成的处理程序

Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
事件处理程序如下所示

private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
    if (status == AsyncStatus.Canceled)
    {
        return;
    }
    CommentsItemsControl.ItemsSource = Comments.Result;
    CommentScrollViewer.ScrollToVerticalOffset(0);
    CommentScrollViewer.Visibility = Visibility.Visible;
    CommentProgressRing.Visibility = Visibility.Collapsed;
}

使用此路由,所有处理都已经为您完成,当任务被取消时,它只会触发事件处理程序,您可以查看它是否被取消。

或者,为了避免修改
slowFunc
(例如,您没有访问源代码的权限):


一个尚未涉及的案例是如何在异步方法内部处理取消。举个简单的例子,您需要将一些数据上传到服务,让它计算一些数据,然后返回一些结果

public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}

请注意,这不会停止您等待的任务,它将继续运行。您需要使用不同的机制来停止它,例如示例中的
CancelAsync
调用,或者最好将相同的
CancellationToken
传递给
任务
,以便它最终能够处理取消。正在尝试中止线程。

哇,好消息!这非常有效,现在我需要弄清楚如何在异步方法中处理异常。谢谢你,伙计!我会读你建议的东西。嘿,伙计,如果我不能使用慢速方法,有办法吗?例如,假设slowFunc位于黑盒中,您只能调用该方法,但不能修改其中的任何内容?不。大多数长时间运行的同步方法都有取消它们的方法—有时通过关闭基础资源或调用另一个方法
CancellationToken
具有与自定义取消系统交互所需的所有钩子,但没有任何东西可以取消不可取消的方法。对。我建议您在
async
方法中从不使用
Wait
Result
;您应该始终使用
wait
,它可以正确地打开异常。奇怪的是,为什么没有一个示例使用
CancellationToken.IsCancellationRequested
,而建议抛出异常?看起来非常棘手。。。整个异步等待实现。我不认为这样的构造会使源代码更具可读性。谢谢你,注意一点——注册令牌应该稍后处理,第二点——使用
ConfigureAwait
,否则你可能会在UI应用程序中受到伤害。@astrowalker:是的,确实令牌的注册最好取消注册(处理)。这可以通过在Register()返回的对象上调用dispose,在传递给Register()的委托内完成。然而,由于“源”标记在这种情况下仅为本地标记,因此所有内容都将被清除……实际上,它所需要的只是使用@astrowalker;-)将其嵌套在
中是的,事实上你是对的。在这种情况下,这是更简单的解决方案!但是,如果您希望直接(不等待)返回Task.when,则需要其他内容。我这样说是因为我曾经遇到过这样一个重构问题:在我使用之前…等待。然后我删除了wait(以及函数上的async),因为它是唯一的一个,没有注意到我完全破坏了代码。由此产生的bug很难找到。因此,我不愿意将using()与async/await一起使用。我觉得Dispose模式无论如何都不适合异步的东西…刚刚发现这有助于我理解取消的各种方式。请注意,虽然这会取消等待任务,但不会取消实际任务(例如,
UploadDataAsync
可能会在后台继续,但一旦完成,它就不会调用
CalculateAsync
,因为该部分已经停止等待)。这可能对您有问题,也可能不会有问题,尤其是如果您想重新启动
await Task.WhenAny(task, source.Token.AsTask());
public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}
public static class TaskExtensions
{
    public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        await Task.WhenAny(task, token.WhenCanceled());
        token.ThrowIfCancellationRequested();

        return await task;
    }

    public static Task WhenCanceled(this CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<bool>();
        cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
        return tcs.Task;
    }
}
public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
    Client client;
    try
    {
        client = await GetClientAsync().WaitOrCancel(token);
        await client.UploadDataAsync(data).WaitOrCancel(token);
        await client.CalculateAsync().WaitOrCancel(token);
        return await client.GetResultsAsync().WaitOrCancel(token);
    }
    catch (OperationCanceledException)
    {
        if (client != null)
            await client.CancelAsync();
        throw;
    }
}