如何在不阻塞UI的情况下等待任务完成?

时间:2022-01-10 01:05:30

this is my first question in this forum and since I'm not a native english speaker I hope you'll go easy on me, in case I'm doing or saying something wrong.

这是我在这个论坛上的第一个问题,因为我不是一个母语是英语的人,我希望你能对我宽容一些,以防我说错了什么。

So, here is my question:

我的问题是:

I like to solve a simple problem (so it seems), but I'm already researching for over a week and tried different things but none of it worked so far, I always stumbled over the same problem.

我喜欢解决一个简单的问题(看起来是这样),但我已经研究了一个多星期,尝试了不同的方法,但迄今为止都没有成功,我总是遇到同样的问题。

What I like to do is opening a progress form, then starting two long running tasks (for instance getting data from two databases). These long running tasks report their progress to the progress form, after they're finished the progress form closes and another form opens to show the results of the two long running tasks. Both tasks have to complete (or cancel/fail) before the progress form closes and the program goes on (or closes).

我喜欢做的是打开一个进度表单,然后启动两个长期运行的任务(例如从两个数据库获取数据)。这些长时间运行的任务报告它们在进度表单上的进展,在完成了进度表单和另一个表单后,显示了两个长期运行任务的结果。这两个任务都必须完成(或取消/失败),然后才会关闭进程,程序继续(或关闭)。

So, the progress form (Form1) only contains two listboxes (ListBox1 and ListBox2) and the following code:

因此,进度表单(Form1)只包含两个列表框(ListBox1和ListBox2)和以下代码:

Public Delegate Sub ShowProgressDelegate(ByVal message As String)

Public Class Form1

Public Sub AddMessage1(ByVal message As String)
  If String.IsNullOrEmpty(message) Then
    Exit Sub
  End If

  If Me.InvokeRequired Then
    Me.Invoke(New ShowProgressDelegate(AddressOf Me.AddMessage1), New Object() {message})
  Else
    Me.ListBox1.Items.Add(message)
    Me.ListBox1.SelectedIndex = Me.ListBox1.Items.Count - 1
    Application.DoEvents()
  End If
End Sub

Public Sub AddMessage2(ByVal message As String)
  If String.IsNullOrEmpty(message) Then
    Exit Sub
  End If

  If Me.InvokeRequired Then
    Me.Invoke(New ShowProgressDelegate(AddressOf Me.AddMessage2), New Object() {message})
  Else
    Me.ListBox2.Items.Add(message)
    Me.ListBox2.SelectedIndex = Me.ListBox2.Items.Count - 1
    Application.DoEvents()
  End If
End Sub

End Class

Then I have my TestClass which simulates the long running tasks and raises progress events:

然后,我的TestClass模拟了长时间运行的任务,并提出了进展事件:

Imports System.Threading

Public Class TestClass

  Public Event ShowProgress(ByVal message As String)

  Private _milliSeconds As UShort

  Public Sub New(milliSeconds As UShort)
    _milliSeconds = milliSeconds
  End Sub

  Public Function Run() As UShort
    For i As Integer = 1 To 20
      RaiseEvent ShowProgress("Run " & i)
      Thread.Sleep(_milliSeconds)
    Next i

    Return _milliSeconds
  End Function

End Class

And finally I have my Main procedure which tries to put these two together:

最后,我的主要程序试图把这两个放在一起:

Imports System.Threading.Tasks
Imports System.ComponentModel

Public Class Start
  Private Const MULTI_THREAD As Boolean = True

  Public Shared Sub Main()
    Dim testClass(1) As TestClass
    Dim testTask(1) As Task(Of UShort)
    Dim result(1) As UShort

    testClass(0) = New TestClass(50)
    testClass(1) = New TestClass(100)

    Using frm As Form1 = New Form1
      frm.Show()

      AddHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1
      AddHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2

      If MULTI_THREAD Then
        testTask(0) = Task(Of UShort).Factory.StartNew(Function() testClass(0).Run)
        testTask(1) = Task(Of UShort).Factory.StartNew(Function() testClass(1).Run)

        Task.WaitAll(testTask(0), testTask(1))

        result(0) = testTask(0).Result
        result(1) = testTask(1).Result
      Else
        result(0) = testClass(0).Run
        result(1) = testClass(1).Run
      End If

      RemoveHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1
      RemoveHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2

      frm.Close()
    End Using

    MessageBox.Show("Result 1: " & result(0) & "; Result 2: " & result(1))
  End Sub

End Class

If I set the constant MULTI_THREAD to false, everything works fine (but sequentially). But if I set if to true, it only shows the form, but no progress and it never shows the resulting message box either. If I debug it never reaches the line after Task.WaitAll(...).

如果我将常数的MULTI_THREAD设置为false,那么一切都可以正常工作(但是顺序地)。但是如果我设置为true,它只显示表单,但没有任何进展,它也不会显示结果消息框。如果我调试它,它永远不会到达任务结束后的线。

I already tried other approaches to multi threading like working with plain threads (no return values), backgroundworkers (only percentage progress, no text messages), BeginInvoke/EndInvoke on Delegates, but nothing worked or it showed the same behaviour like described above.

我已经尝试过其他方法,比如使用普通线程(没有返回值)、backgroundworkers(只取得百分比进度、没有文本消息)、开始调用/EndInvoke,但是没有任何工作或它显示了如上所述的相同行为。

I was told in another forum, that Task.WaitAll blocks the UI-thread since it was called on the UI-thread, but I can't believe that since MSDN clearly states that Task.WaitAll waits for the handed-over tasks to finish or fail and I didn't hand over the UI thread.

我在另一个论坛上被告知这项任务。WaitAll阻塞了ui线程,因为它是在ui线程上调用的,但是我不能相信,因为MSDN清楚地声明了这个任务。WaitAll等待着完成或失败的任务,而我没有提交UI线程。

So, I don't know what else to try, so I hope you can point out my mistake to me or to show me another way to try to solve my little problem.

所以,我不知道还有什么可以尝试的,所以我希望你能指出我的错误,或者告诉我另一种方法来解决我的小问题。

Thank you very much in advance.

非常感谢你的帮助。

Update:
I did a little bit more testing and debugging. If I change the two "Me.Invoke(...)" in the code of my Form to "Me.BeginInvoke(...)" then I still get no progress messages shown in my form, but at least the tasks work and I get my closing message box. So the actual delivering or showing of the progress messages seems to cause the problem.

更新:我做了一些测试和调试。如果我将表单中的两个“Me.Invoke(…)”改为“. begininvoke(…)”,那么我的表单中仍然没有显示任何进展消息,但至少任务是工作的,并且我得到了我的关闭消息框。因此,实际交付或显示进度消息似乎会导致问题。

Maybe this triggers something in your minds?

也许这触动了你的思想?

Thank you again.

再次感谢你。

Update II:
After a lot of trial and error it seems to work, at least a little bit. Instead of the "Me.InvokeRequired" parts in the code of my form I handed over "TaskScheduler.FromCurrentSynchronizationContext" when creating the tasks. So I at least got my progress messages shown, but the tasks still don't run completely parallel. But that's a topic for my next question here.

更新II:经过大量的尝试和错误之后,它似乎起了作用,至少有一点。而不是“我。InvokeRequired“在我的表单的代码中,我移交了”TaskScheduler。FromCurrentSynchronizationContext“创建任务时”。因此,我至少得到了显示的进度消息,但任务仍然不能完全并行运行。但这是我下一个问题的主题。

3 个解决方案

#1


0  

you process might taking much of resources available at system side. it is just starting only new task, try starting complete new thread for heavy processes

您可能会在系统方面占用大量资源。它只是启动新任务,尝试启动重进程的新线程。

you can even start your progress UI in separate thread so that it wont stop responding

您甚至可以在单独的线程中启动您的进程UI,以便它不会停止响应。

Thread nThread;
private void Button1_Click(System.Object sender, System.EventArgs e)
{
    nThread = new Thread(ShowDlg);
    nThread.Start();
}

private void ShowDlg()
{
    Form1 nWin = new Form1();
    nWin.ShowDialog();
}

#2


0  

You are calling WaitAll after you start the tasks, which will block the UI thread, thus your progress will not get shown.

您在启动任务后调用WaitAll,它将阻塞UI线程,因此您的进度不会显示出来。

You should specify a callback or continuation task to get executed once the tasks are complete, and do the final stuff in there, e.g. show the results and close the progress form.

当任务完成后,您应该指定一个回调或延续任务来执行,并在其中完成最后的任务,例如显示结果并关闭进度表单。

Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
  result(0) = testTask(0).Result
  result(1) = testTask(1).Result
  RemoveHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1
  RemoveHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2

  frm.Close()
  MessageBox.Show("Result 1: " & result(0) & "; Result 2: " & result(1))
End Sub

I'm not normally a vb.net programmer, so you might have to adjust it a bit to make sure the closures work ok and you marshal some of those calls back to the ui thread

我通常不是一个vb.net程序员,所以您可能需要稍微调整一下,以确保闭包工作正常,并将其中一些调用整理回ui线程。

#3


0  

Try to change Task to BackgroundWorker. This class is created for forms and have nice api to display information on form about your work.Look here for tutorial

试着把任务交给后备工人。这个类是为表单创建的,并且有很好的api来显示关于您的工作的信息。看这里的教程

UPDATE Full example, because in comments it looks ugly:

更新完整的例子,因为在评论中它看起来很丑:

public partial class Form1 : Form
{
    private BackgroundWorker bw = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();

        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if ((e.Cancelled == true))
        {
            this.tbProgress.Text = "Canceled!";
        }

        else if (!(e.Error == null))
        {
            this.tbProgress.Text = ("Error: " + e.Error.Message);
        }

        else
        {
            this.tbProgress.Text = "Done!";
        }
    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.tbProgress.Text = (string)e.UserState;            
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        var worker = sender as BackgroundWorker;

        for (int i = 1; (i <= 10); i++)
        {
            if ((worker.CancellationPending == true))
            {
                e.Cancel = true;
                break;
            }
            else
            {
                // Perform a time consuming operation and report progress.
                System.Threading.Thread.Sleep(500);
                worker.ReportProgress((i * 10), "status you want "+i.ToString());
            }
        }
    }
}

#1


0  

you process might taking much of resources available at system side. it is just starting only new task, try starting complete new thread for heavy processes

您可能会在系统方面占用大量资源。它只是启动新任务,尝试启动重进程的新线程。

you can even start your progress UI in separate thread so that it wont stop responding

您甚至可以在单独的线程中启动您的进程UI,以便它不会停止响应。

Thread nThread;
private void Button1_Click(System.Object sender, System.EventArgs e)
{
    nThread = new Thread(ShowDlg);
    nThread.Start();
}

private void ShowDlg()
{
    Form1 nWin = new Form1();
    nWin.ShowDialog();
}

#2


0  

You are calling WaitAll after you start the tasks, which will block the UI thread, thus your progress will not get shown.

您在启动任务后调用WaitAll,它将阻塞UI线程,因此您的进度不会显示出来。

You should specify a callback or continuation task to get executed once the tasks are complete, and do the final stuff in there, e.g. show the results and close the progress form.

当任务完成后,您应该指定一个回调或延续任务来执行,并在其中完成最后的任务,例如显示结果并关闭进度表单。

Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
  result(0) = testTask(0).Result
  result(1) = testTask(1).Result
  RemoveHandler testClass(0).ShowProgress, AddressOf frm.AddMessage1
  RemoveHandler testClass(1).ShowProgress, AddressOf frm.AddMessage2

  frm.Close()
  MessageBox.Show("Result 1: " & result(0) & "; Result 2: " & result(1))
End Sub

I'm not normally a vb.net programmer, so you might have to adjust it a bit to make sure the closures work ok and you marshal some of those calls back to the ui thread

我通常不是一个vb.net程序员,所以您可能需要稍微调整一下,以确保闭包工作正常,并将其中一些调用整理回ui线程。

#3


0  

Try to change Task to BackgroundWorker. This class is created for forms and have nice api to display information on form about your work.Look here for tutorial

试着把任务交给后备工人。这个类是为表单创建的,并且有很好的api来显示关于您的工作的信息。看这里的教程

UPDATE Full example, because in comments it looks ugly:

更新完整的例子,因为在评论中它看起来很丑:

public partial class Form1 : Form
{
    private BackgroundWorker bw = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();

        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if ((e.Cancelled == true))
        {
            this.tbProgress.Text = "Canceled!";
        }

        else if (!(e.Error == null))
        {
            this.tbProgress.Text = ("Error: " + e.Error.Message);
        }

        else
        {
            this.tbProgress.Text = "Done!";
        }
    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.tbProgress.Text = (string)e.UserState;            
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        var worker = sender as BackgroundWorker;

        for (int i = 1; (i <= 10); i++)
        {
            if ((worker.CancellationPending == true))
            {
                e.Cancel = true;
                break;
            }
            else
            {
                // Perform a time consuming operation and report progress.
                System.Threading.Thread.Sleep(500);
                worker.ReportProgress((i * 10), "status you want "+i.ToString());
            }
        }
    }
}