【问题标题】:How to use async/await with loop infinity?如何将 async/await 与循环无穷大一起使用?
【发布时间】:2019-03-24 18:55:00
【问题描述】:

我想无限循环检查所有时间 n=0 或 n=1

    public int check()
    {

        int n;

        Int32 nTest = 0;
        nTest = RID.Read(obj);

        if (nTest != 0)
        {
            n = 0;
        }
        else
        {
            n = 1;
        }

        return n;
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        Task<int> task = new Task<int>(check);
        task.Start();

        while (true)
        {
            int c = await task;

            label7.Text = c.ToString();
        }
    }

我尝试在 Form1_Load 中为 true 以开始检查 n 值。当我运行程序时,它会冻结并且无法单击任何内容。如何解决?

【问题讨论】:

  • 首先,任务一旦完成,就不能再等待了。因此,您要么每次都需要启动一个新任务(但不是那样),要么将循环放入任务中,然后从那里更新您的 UI(在 UI 上下文中)。后者将是正确的方法。您也应该在周期之间添加一些延迟。如果你的RID.Read 也是异步的,那就更好了。

标签: c# multithreading asynchronous async-await


【解决方案1】:

Async-await 旨在阻止您的程序在相对较短的时间内空闲等待另一个进程完成。考虑等待数据库查询的结果、读取文件、从数据库中获取数据等。

如果您使用 async-await,您的线程不会在其他处理正在执行请求时空闲等待。相反,如果可以环顾四周,看看是否还有其他事情要做。

当然,您可以通过创建一个单独的线程来等待您来实现相同的效果。除此之外,这会使您的代码更加复杂,创建单独的线程相当耗时。

如果你想启动一个长时间运行的进程,想想几秒钟,也许几分钟甚至更长,启动这个单独线程所需的时间不再重要了。

所以在你的情况下,我建议使用BackgroundWorker

如果您的后台工作人员需要大量程序来完成其工作,请考虑从 BackgroundWorker 派生并开始在 BackgroundWorker.OnDoWork 的覆盖下工作。

在您的情况下,backgroundworker 只需要一个小函数,因此订阅 DoWork 事件就足够了。

使用你的 Visual Studio 工具箱添加一个后台工作者,或者在你的构造函数中手动添加它:

// Constructor:
public Form1()
{
    InitializeComponent();

    // Create a backgroundworker
    this.backgroundWorker = new BackgroundWorker
    {
        // only if you want to display something during processing:
        WorkerReportsProgress = true,

        WorkerSupportsCancellation = true; // to  neatly close your form 
    };

    this.backgroundWorker.DoWork += this.BackgroundProcedure;
    this.backgroundWorker.ProgressChanged += this.BackgroundProcessReport;
    this.backgroundWorker.RunworkerCompleted += this.BackgroundWorkerFinished;
}

启动和停止很容易:

bool IsBackGroundWorkerBusy => this.backgroundWorker.IsBusy;
void StartBackgroundWork()
{
    if (this.IsBackGroundWorkerbusy) return; // already started;

    this.DisplayBackgroundWorkerActive(); // for example, show an ajax loader 
    this.backgroundWorker.RunworkerAsync();

    // or if you want to start with parameters:
    MyParameters backgroundWorkerParameters = new MyParameters(...);
    this.backgroundWorker.RunworkerAsync(backgroundWorkerParameters);

}
void RequestCancelBackgroundWork()
{
    this.DisplayBackgroundWorkerStopping();
    this.backgroundWorker.CancelAsync();
}

后台程序。它由后台线程执行。 您不能在此过程中调用任何与 UI 相关的函数。 如果您想更新 UI 中的任何内容,请使用 ReportProgress。

void BackGroundProcedure(object sender, DoworkEventArgs e)
{
    // if you know that the backgroundworker is started with parameters:
    MyParameters parameters = (MyParameters)e.Argument;

    // do you work, regularly check if cancellation is requested:
    while (!e.Cancel)
    {
        ...

        // only if progress reports are needed: report some progress, not too often!
        MyProgressParams progressParams = new MyProgressParams(...);
        this.ReportProgress(..., progressParams);
    }

    // if here, the thread is requested to cancel
    // if needed report some result:
    e.Result = ...;
}

进度报告。它由您的 UI 线程执行,因此如果需要,您可以更新 UI 元素。这是此方法的主要功能。

报告进度中的第一个参数是一个数字,通常从 0..100 开始,进度事件的接收者使用它来更新有关进度的一些视觉显示。如果您没有任何迹象表明进度需要多长时间,请不要使用该值。 progressParams 可以是任何对象。

void BackgroundProcessReport(object sender, ProgressChangedEventArgs e)
{
     // the background worker reported some progress.
     ... // update UI
}

Runworker Completed 在线程完成时调用。它包含分配给 e.Result 的数据。它由 UI 线程执行,所以你可以做任何你想做的与 UI 相关的事情:

void BackgroundWorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
    this.DisplayBackgroundWorkerFinished(); // for example: hide ajax loader

    ... // use e to process result
}

表格的整齐闭合

如果您的表单正在关闭,则必须在处理窗口之前完成后台工作程序。正确的方法是使用 OnClosing 事件:

bool closeFormRequested = false;

void OnFormClosing(object sender, CancelEventArgs e)
{
     // if background worker busy: request cancellation; can't close the form right now
     if (this.IsBackgroundworkerBusy)
     {
         this.closeFormRequested = true;
         e.Cancel = true;
         this.RequestCancelBackgroundWork();
     }
     else
     {   // background worker not busy, OK to close
         e.Cancel = false;
     }
}

一段时间后,后台工作人员报告它已完成:

void BackgroundWorkerFinished(object sender, RunWorkerCompletedEventArgs e)
{
    this.DisplayBackgroundWorkerFinished(); 
    ... // process result

   // if requested: close the form:
   if (this.closeFormRequested)
       this.Close();
}

this.Close()会导致OnFormClosing,不过这次后台worker不会忙,会继续关闭

最后:后台工作者实现了 IDisposable,当你的表单被 Disposed 时不要忘记 Dispose。

【讨论】:

    【解决方案2】:

    您应该使用 Microsoft 的响应式框架(又名 Rx)- NuGet System.Reactive.Windows.FOrms 并添加 using System.Reactive.Linq; - 然后您可以这样做:

    private async void Form1_Load(object sender, EventArgs e)
    {
        IDisposable subscription =
            Observable
                .While(() => true, Observable.Start(() => RID.Read(obj) == 0 ? 1 : ))
                .ObserveOn(this)
                .Subscribe(c => label7.Text = c.ToString());
    }
    

    这是你的全部代码。

    【讨论】:

      【解决方案3】:

      当我运行程序时,它会冻结,不能点击任何东西。

      这是因为任务只启动一次。完成后,它保持完成,因此第一个 await 异步等待任务完成,但所有其他 awaits 只是立即同步返回第一个值。这就是为什么你不能点击任何东西的原因:你最终会在你的 UI 线程上出现一个同步的无限循环。

      要解决这个问题,我建议使用Task.Run 来启动后台任务。 Task.Run 比任务构造函数和Start 更不容易出错。

      private async void Form1_Load(object sender, EventArgs e)
      {
        while (true)
        {
          int c = await Task.Run(() => check());
          label7.Text = c.ToString();
        }
      }
      

      或者,如果您希望后台代码中的无限循环,您可以使用IProgress&lt;T&gt; 向 UI 线程报告进度:

      private Task CheckForever(IProgress<int> progress)
      {
        while (true)
        {
          var c = check();
          progress?.Report(c);
        }
      }
      
      private async void Form1_Load(object sender, EventArgs e)
      {
        var progress = new Progress<int>(c => label7.Text = c.ToString());
        await CheckForever(progress);
      }
      

      请注意,Task.Run 代码总是比 BackgroundWorker 代码更干净、更易于维护且类型安全。

      【讨论】:

        猜你喜欢
        • 2022-06-23
        • 2020-09-24
        • 2021-02-27
        • 1970-01-01
        • 2014-03-13
        • 2017-07-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多