【问题标题】:Exiting from async infinite loops退出异步无限循环
【发布时间】:2017-09-30 10:53:04
【问题描述】:

我在我的 WinForm 应用程序中启动了一些异步无限循环,但每次我试图摆脱它们时,程序都会挂起。我读过一些类似的主题,人们建议使用 CancellationTokens,但我无法根据我的需要调整它们。这是我的代码的相关部分。

static bool processStop = false;
static bool processStopped = false;

//Called once 
private async void ProcessData()
{
    while (!processStop)
    {
        await Task.Run
        (
            () =>
            {
                //Do stuff and call regular not async methods
            }
        );
    }
    processStopped = true;
}

//Button click handler to exit WinForm
btnExit.Click += (senders, args) =>
{
    processStop = true;
    //Programm hangs up here

    while (!processStopped);

    FormMain.Close();                
}

修改了代码
变量是静态的。
Close 方法是 Forms 的默认 Close() 方法。

【问题讨论】:

  • 我不认为问题是由您显示的代码引起的,如果您能向我们展示 Close 方法的代码就更好了。
  • 这个代码好像也适应不了,怎么不问怎么用cancel token呢?
  • @AbdullahDibas 编辑代码使其更清晰。
  • @Crowcoder 因为我现在正在研究多任务处理,所以我不确定 CancellationTokens 是否是解决我的问题的最佳答案。另外我还不熟悉 CancellationTokens,所以我要求一个更一般的答案。如果可能的话。
  • 试试await Task.Run(() =>{...}).ConfigureAwait(false);

标签: c# winforms loops asynchronous exit


【解决方案1】:

问题是对Task.Run 的调用在主线程上继续进行。 processStop = true;while (!processStopped); 一个接一个地同步执行。这不会让ProcessData 方法继续执行并发生死锁。
我看到了几个解决方案:

  • ConfigureAwait(false)Task.Run 一起使用:

    private async void ProcessData()
    {
        while (!processStop)
        {
            await Task.Run
            (
                () =>
                {
                    //Do stuff and call regular not async methods
                }
            ).ConfigureAwait(false);
        }
        processStopped = true;
    }
    

    这将导致ProcessData 在线程池上继续,而您已经通过调用Task.Run 使用了线程池,所以这不是一个很好的解决方案

  • 将整个过程包裹在Task.Run

    static volatile bool processStop = false;
    static volatile bool processStopped = false;
    
    //Called once 
    private async void ProcessData()
    {
        await Task.Run(() =>
        {
            while (!processStop)
            {
                ...
            }
            processStopped = true;
        });
    }
    

    这将需要更改传递的方法的形式以使用其中的循环。

  • ProcessData 设为同步方法来处理CPU 密集型任务并正确调用它。 CancellationToken 将是取消任务的首选方式:

    private void ProcessData(CancellationToken token)
    {
        while(!token.IsCancellationRequested)
        {                
            // do work
        }
    }
    

    然后用这个来调用它:

    Task processingTask;
    CancellationTokenSource cts;
    
    void StartProcessing()
    {
        cts = new CancellationTokenSource();
        processingTask = Task.Run(() => ProcessData(cts.Token), cts.Token);
    }
    
    btnExit.Click += async (senders, args) =>
    {
        cts.Cancel();
        try
        {
            await processingTask;
        }
        finally
        {
            FormMain.Close();
        }                
    }
    

【讨论】:

  • 正确使用 volatile 会获得积分,但最后几行代码搞砸了:FormMain.Close() 永远不会被调用,因为 await processingTask 会抛出 OperationCanceledException
  • @KirillShlenskiy 它不会因为该方法优雅地完成,我不使用token.ThrowIfCancellationRequested(),但我只是检查取消。当然也可能有其他例外,所以无论如何它都应该用try{} finally{} 包裹起来。
  • 啊,当然:我看了一眼!token.IsCancellationRequested 支票。将它用作async Task 的一部分是相当不寻常的(与async void 相反,它更有意义)。不过,try/finally 绝对是个好主意。
  • 是的,我只是将它用作异步传播取消请求的一种简单方法,还有其他方法可以做到这一点。
  • @JakubDąbek 你的回答超出了我的要求。您的所有三个建议都足够详细。我尝试了所有这些,但只有第一个和第三个有效。不过没关系。我最终使用了 CancellationToken,这似乎是处理异步任务的“更专业”的方式。谢谢 Jakub,当然还有大家的帮助!
【解决方案2】:

如果你想在不阻塞的情况下旋转一堆任务,你可以这样做:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //Called once 
        private async Task ProcessData()
        {
            int count = 0;
            while (true)
            {
                await Task.Run
                 (
                     () =>
                     {
                         this.Invoke(new Action(() => {
                             label2.Text = (count++).ToString();
                             label1.Text = DateTime.Now.ToString(); }));
                         Thread.Sleep(100);
                     }
                 );
            }
            Debugger.Break(); //you will never see this hit at all
        }
        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private async void button2_Click(object sender, EventArgs e)
        {
           await ProcessData();
        }
    }
}

【讨论】:

  • 我不能当场破坏 ProcessData(),因为它使用串行端口和 SQL 查询。我必须在所有处理周期完成后停止它,否则我不会能够处理一些我已经用我的例程处理的错误。
  • 我明白了。您可能想要启动线程并使用Join() 让它们完成,并且可能需要一个信号量门来防止过多的线程使系统不堪重负。
猜你喜欢
  • 2016-11-07
  • 2020-05-11
  • 1970-01-01
  • 2020-06-06
  • 2017-01-20
  • 2017-12-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多