如何在C# Winforms应用程序中取消执行长时间运行的异步任务

7

我是一名经验较少的C#程序员,需要在程序流程管理方面寻求帮助。这是一个WinFormApp,它会要求用户输入多个参数,并使用这些参数建立串口通信以进行测量。这些测量在一个异步方法中进行,大约需要20分钟才能完成。因此,我正在使用

    void main()
    {
        //Setup
    }

    private void button1_Click(object sender, EventArgs e)
    {
        await measurements();
        //Plot results
    }

    private async Task measurements()
    {
        while(true){
        //Make some measurements
        await Task.Delay(120000);
        //Make measurements, present data in the UI form.
        //return if done
        }
    }

现在我需要添加一个按钮,以便用户可以取消测量以更改输入或参数,然后重新开始测量。因此,我添加了一个“取消”按钮。

    private void button7_Click(object sender, EventArgs e)
    {
        textBox64.Enabled = true;
        button6.Enabled = true;
        button5.Enabled = true;
        textBox63.Enabled = true;
        button3.Enabled = true;
        trackBar1.Enabled = true;
        timer.Enabled = true;
        button7.Enabled = false;
        clearData();
        // measurement.Stop();            
    }

现在我不知道如何管理程序的流程。我尝试在button1_Click()中创建一个try-catch结构,并从button7_Click()抛出异常,但它无法传递到button1_Click()
然后我尝试在新线程上运行measurements()。但该线程无法访问主窗体上的70多个UI元素。
即使我不会降至使用Goto的地步。
我需要的是建议,您将如何编写程序以在此情况下对应用程序进行良好的控制,而不会通过风险较高的异常和Goto来危及程序的流程。

2
将您的 while(true) 循环更改为 while(measuring),并在应该停止时设置 measuring=false - AntiHeadshot
还可以阅读异常的相关知识——什么是异常,它们如何工作以及为什么它们不是“危险的东西”。 - PaulF
AntiHeadShot:谢谢,我考虑过这个问题,但是每次循环迭代需要太多时间。我希望UI能够很快地响应用户的操作。PaulF 9:是啊,我知道异常机制没问题,但它们不应该控制程序流程。那就是我的意思! :) - NoobPointerException
@NoobPointerException,那是不正确的。异常是用于信号传递异常控制流的。 - Aron
2个回答

5
如果你想在任务执行过程中取消实际任务,则需要考虑使用CancellationTokenSource并将取消标记传递到异步方法中。
这是Microsoft文档,其中底部有一个很好的示例,这是另一篇很好的博客,展示了进度条和允许取消。第二篇文章有一个很好的概述:
取消操作由CancellationToken结构控制。您可以在可取消的异步方法的签名中公开取消令牌,使它们能够在任务和调用者之间共享。在最常见的情况下,取消按照以下流程进行:
  1. 调用者创建一个CancellationTokenSource对象。
  2. 调用可取消的异步API,并传递来自CancellationTokenSource的CancellationToken(CancellationTokenSource.Token)。
  3. 调用者使用CancellationTokenSource对象请求取消(CancellationTokenSource.Cancel())。
  4. 任务确认取消并取消自身,通常使用CancellationToken.ThrowIfCancellationRequested方法。
为了使应用程序快速响应取消请求,您需要在长时间运行的方法中定期检查取消令牌,并根据请求做出相应的响应。

2
我现在已经让它运行得相当不错了,但是必须创建一个类似于以下结构的结构; for (int wait = 0; wait < 120; wait++) { await Task.Delay(1000); ct.ThrowIfCancellationRequested(); } - NoobPointerException

2

这是一些粗略的代码,但应该能够完成任务。

使用CancellationToken。 我已经使用了一个切换方法来测试异步任务是否被取消。

CancellationTokenSource cts;

private async button1_Click(object sender, EventArgs e)
{
    toggleAsyncTask(false)
}

private void toggleAsyncTask(bool isCancelled)
{
    if(cts==null)
        var cts = new CancellationTokenSource();
    if(!isCancelled)
    {
        await measurements(cts.Token);
    }
    else
    {
        cts.Cancel();
        cts.Dispose();
        cts = null;
    }
}

private async Task measurementsOne(CancellationToken token)
{
    try
    {
        while(true){
            //Make some measurements
            await Task.Delay(120000); // don't know if you need this.
            //Make measurements, present data in the UI form.
            //return if done
    }
    catch(OperationCancelledException)
    {
        // to do if you please.
    }
}

private void button7_Click(object sender, EventArgs e)
{
    // button stuff
    toggleAsyncTask(true); // isCancelled is true.          
}

谢谢!你和tomRedox的回答几乎是一样的。我实现了取消令牌结构,但似乎不够用。看起来cts.Cancel()没有抛出异常。测量()方法瞬间被中断,但然后继续执行。 - NoobPointerException
它甚至没有短暂的中断,只是持续不断地运行。 - NoobPointerException
await Task.Delay(120000, token)? 如果这不抛出异常,那么在 Delay 语句后添加 if (token.IsCancellationRequested) token.ThrowIfCancellationRequested(); - Geoffrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接