如何使用异步/等待循环无限?

时间:2019-03-24 18:55:00

标签: c# multithreading asynchronous async-await

我想循环无穷大以检查所有时间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值。当我运行程序时,它将冻结并且无法单击任何东西。如何解决?

3 个答案:

答案 0 :(得分:0)

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

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

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

如果您想启动一个长时间运行的进程,想想几秒钟甚至几分钟甚至更长的时间,那么启动该单独线程所需的时间就不再重要了。

因此,根据您的情况,我建议您使用BackgroundWorker

如果后台工作人员需要很多程序来完成工作,请考虑从BackgroundWorker派生,并以BackgroundWorker.OnDoWork的替代字词开始工作。

在您的情况下,背景工作人员仅需要一个小的功能,因此足以订阅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,但是这次后台工作人员不会很忙,并且关闭将继续

最后:后台工作人员实现了IDisposable,不要忘了在处理表单时将其处理。

答案 1 :(得分:0)

您应该使用Microsoft的Reactive Framework(又名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());
}

这就是您的全部代码。

答案 2 :(得分:0)

  

当我运行程序时,它会冻结并且无法单击任何东西。

这是因为任务仅启动一次。完成后,它会保持完成,因此第一个await异步地等待任务完成,但是所有其他await都只是立即并同步地返回了第一个值。这就是为什么您无法单击任何东西的原因:您最终在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<T>向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代码更整洁,易于维护并且类型安全。