【问题标题】:CancellationToken is even nullCancellationToken 甚至为空
【发布时间】:2018-10-31 01:40:17
【问题描述】:

在我的应用程序中,我运行了一个执行繁重操作的Task,我试图停止执行该Task。实际上我在课堂上声明了以下结构:

public class Foo
{
    private CancellationTokenSource tokenSource = new CancellationTokenSource();
    private CancellationToken token;

    public void Start()
    {
        var task = new Task(async () => {
           try
           {
               await new Bot().StartAsync(token);
           }
           catch(Exception ex)
           {
              Console.WriteLine(ex.ToString());
           }
        }, token));

        task.Start();
    }
}

如您所见,我声明了一个CancellationTokenSource,它允许我在用户单击按钮时停止任务执行:

StopTask_Click(object sender, EventArgs e) 
{
    tokenSource.Cancel();
}

现在,在 StartAsync 方法中,我有以下代码:

public async Task StartAsync(CancellationToken token)
{
    ApplicationToken = token;

    while(true)
    {
       if(ApplicationToken.IsCancellationRequested)
       {
           break;
       }
    }
}

ApplicationToken 将作为参数传递的令牌存储在StartAsync 方法的类中。

在按钮点击事件之后,请求应该被取消但没有任何反应。

然后我检查每次迭代是否有取消请求,但变量值甚至是false

【问题讨论】:

  • 您可能需要澄清问题的标题。由于CancellationTokenstruct,它不能是null
  • 我猜你传递的是不同的令牌。为什么不在Foo 类中创建Cancel 方法。所以要取消任务只需调用foo.Cancel(); BTW 我在这里看不到StartAsync 的实现和Foo 类之间的关系。
  • @Luthfay StartAsync 方法是另一个类的一部分,Foo 类只声明了一个包含繁重操作的Task
  • 抱歉,没看到……
  • “重操作”是什么意思?不管while 循环中发生了多少事情,在循环循环之前它肯定不会检查取消。

标签: c# task


【解决方案1】:

嗯,你好像忘了token = tokenSource.Token;

Edit1:您应该使用ThrowIfCancellationRequested()检查取消

编辑2:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace CancellationTokenPOC
{
class Program
{
    public static async Task Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        TokenPOC t = new TokenPOC();
        var longRunningTask = Task.Factory.StartNew(async () =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
                t.token.ThrowIfCancellationRequested();

                await Task.Delay(10000);
            }

        });
        Console.ReadKey();
        t.source.Cancel();
        await Task.Delay(1000);
        Console.WriteLine("finishing");
    }
}

class TokenPOC
{
    public CancellationTokenSource source = new CancellationTokenSource();
    public CancellationToken token;
    public TokenPOC()
    {
        token = source.Token;
    }
}
}

此令牌被取消并按预期结束程序...

【讨论】:

  • 我实现了@sean 逻辑,但还是同样的问题
  • 这个不行,我怀疑是因为你提出的方法需要在主线程上使用,我需要停止一个任务的执行
  • @JuveMerda 试试我的代码,它是一个简单的控制台应用程序...我认为您没有显示的代码有问题...
  • 好的,我明白为什么这对我不起作用。本质上,我试图停止在 Foo 类的任务中运行的任务。我试图停止的任务是Bot 类的一部分,因此取消令牌需要传播该任务中的值,而不是在主线程中
  • 如果这解决了您的问题,请不要忘记标记为答案
【解决方案2】:

您传递的令牌与您尝试取消的令牌不同。你可以消除

private CancellationToken token;

改为传递给StartAsync()

tokenSource.Token

【讨论】:

  • @JuveMerda 您需要使用Start() 开始任务或使用Task.Run()
  • 任务开始了,我就是问题里的代码不写了,我来更新一下
【解决方案3】:

你可以试试下面的:

public class Foo
{
   var cancellationTokenSource = new CancellationTokenSource();
   var token = cancellationTokenSource.Token();

   public void Start() {
        var t = Task.Factory.StartNew(() =>
        {
            try
            {
               await new Bot().StartAsync(token);
            }
            catch(Exception ex)
            {
               Console.WriteLine(ex.ToString());
            }
        }, token).ContinueWith(task =>
          {
              if (!task.IsCompleted || task.IsFaulted)
              {
                 // Log
              }
          }, token);
    }

    StopTask_Click(object sender, EventArgs e) 
    {
          cancellationTokenSource.Cancel();
    }
}

【讨论】:

    【解决方案4】:

    首先,StartAsync 缺少 await 运算符,它将同步运行。

    public async Task StartAsync(CancellationToken token)
    {
        ApplicationToken = token;
    
        while (true)
        { 
            if (ApplicationToken.IsCancellationRequested) break;
            await Task.Delay(10, ApplicationToken); // => you may change it to concrete task
        }
    }
    

    其次,Foo 中的 CancellationToken 类是多余的,你甚至不需要初始化它。

    new Bot().StartAsync(tokenSource.Token); 内部的 Start 方法不等待,因此在调用完成之前继续执行当前方法。

    由于Start 方法无效,您应该使用GetAwaiter() 方法来执行Bot 类的任务。

    public void Start()
    {
        try
        {
            new Bot().StartAsync(tokenSource.Token).GetAwaiter();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
    

    最后,如果StopTask_Click 方法是Foo 类的成员,那么它应该可以正常工作。


    这是一个如何使用CancellationTokenSource取消任务的示例

    public partial class Form1 : Form
    {
        private readonly Foo foo = new Foo();
    
        private void button1_Click(object sender, EventArgs e)
        {
            foo.Start();          
        }
    
        private void button2_Click(object sender, EventArgs e)
        {
            foo.Cancel();
        }
    }
    
    public class Foo
    {
        private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
    
        public void Start()
        {
            try
            {
                new Bot().StartAsync(tokenSource.Token).GetAwaiter();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    
        public void Cancel()
        {
            tokenSource.Cancel();
        }
    }
    
    public class Bot
    {
        private CancellationToken ApplicationToken;
    
        public async Task StartAsync(CancellationToken token)
        {
            ApplicationToken = token;
    
            while (true)
            { 
                if (ApplicationToken.IsCancellationRequested) break;
                await Task.Delay(10, ApplicationToken);
            }
        }
    }
    

    【讨论】:

    • 问题仍然存在,但我注意到这一点:Foo 类中的令牌在单击按钮后将 IsCancellationRequested 设置为 true,而在 StartAsync 函数中作为参数传递的令牌仍然为 false。所以似乎代码没有反映变化? ——
    • 您不需要将IsCancellationRequested 设置为true。只需致电tokenSource.Cancel(); 您能指出 StopTask_Click 的位置吗?它是 Foo 类的成员吗?还是?
    • 是的,是Foo 类的一部分,只有StartAsyncBot 类的一部分
    • 我已经在我的环境中重现了您的问题,它对我来说工作正常。
    • 好的,我明白为什么这对我不起作用。本质上,我试图停止在 Foo 类的 Task 中运行的 Task 。我试图停止的任务是 Bot 类的一部分,因此取消令牌需要传播该任务中的值,而不是在主线程中
    猜你喜欢
    • 2017-02-15
    • 2022-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 2022-08-04
    相关资源
    最近更新 更多