【问题标题】:Async-Await Progress Bar and Cross Threading Issue异步等待进度条和跨线程问题
【发布时间】:2015-09-10 14:07:57
【问题描述】:

我一直在使用 C# 中的 async 和 await 制作进度条。我用以下代码制作了一个简单的表单Form1.cs

public partial class Form1 : Form
{
    private Progress<TaskAsyncExProgress> progress;
    public Form1(Progress<TaskAsyncExProgress> progress = null)
    {
        InitializeComponent();
        if (progress == null)
        {
            this.progress = new Progress<TaskAsyncExProgress>();
        }
        else
        {
            this.progress = progress;
        }

        this.progress.ProgressChanged += (s, e) =>
        {
            Debug.WriteLine("Progress: " + e.ProgressPercentage + "%");

            progressBar.Value += e.ProgressPercentage;

            txtResult.Text += e.Text;
        };

        Shown += async (s, e) =>
        {
            try
            {
                await Task.Run(() => HardTask(progress));
                txtResult.Text += "Done!";
            }
            finally
            {
                MessageBox.Show("Done!");
                Close();
            }
        };
    }

    void HardTask(Progress<TaskAsyncExProgress> progress)
    {
        incrementProgress();
        Thread.Sleep(5000);
    }
    public void incrementProgress()
    {
        Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                var args = new TaskAsyncExProgress();
                args.ProgressPercentage = 1;
                args.Text = i.ToString() + " "; 
                ((IProgress<TaskAsyncExProgress>)progress).Report(args);
                Thread.Sleep(10);
            }
        }
        );
    }
}


public class TaskAsyncExProgress
{
    public int ProgressPercentage { get; set; }

    public string Text { get; set; }
}

在我的Program.cs,我有两种情况:

// Case 1: This causes a cross-thread issue
//Progress<TaskAsyncExProgress> progress = new Progress<TaskAsyncExProgress>();
//Form1 frm =  new Form1(progress);

// Case 2: This works fine
Form1 frm =  new Form1();

frm.ShowDialog();

为什么案例 1 会导致跨线程问题,但案例 2 可以正常工作?如果您想在表单本身之外使用 Report 方法,我觉得案例 1 可能是一个非常有用的构造。

编辑:我知道我可以使用 BeginInvoke 或类似的,但我的印象是 Async+Await 应该可以做到,所以我不再需要这样做了。

【问题讨论】:

  • stackoverflow.com/questions/17972268/… 中提供的大部分解释和工作示例(几乎重复)。 Servy 回答的确切问题(创建 Progress before 同步上下文创建)(也有许多类似的问题,还没有好的重复目标)。

标签: c# async-await


【解决方案1】:

Progress 类在创建时捕获 SynchronizationContext.Current 的值,并使用该值(或默认同步上下文,如果没有值)发布进度更改事件处理程序。

在您的第一个示例中,您在创建同步上下文之前创建了Progress 实例。在第二个示例中,您在创建同步上下文之后创建它,这就是它起作用的原因。

【讨论】:

  • 这是否意味着 SynchronizationContext 是在制作表单时创建的?
  • @Ringil 不,它是在您启动消息循环时创建的。通常您应该使用Application.Run 来启动您的应用程序循环,尽管您使用的是ShowDialog
  • 好吧,我试图做一个简单的例子来调试另一个问题中的这个问题。其他程序使用 ShowDialog,但我认为这并不重要,因为它也会创建一个消息循环。
【解决方案2】:

对 UI 的更新必须在 UI 线程中进行。考虑一下:

this.progress.ProgressChanged += (s, e) =>
    {
        Dispatcher.BeginInovoke(() => {
            Debug.WriteLine("Progress: " + e.ProgressPercentage + "%");

            progressBar.Value += e.ProgressPercentage;

            txtResult.Text += e.Text;
        });
    };

编辑:这适用于所有 UI 更新,而不仅仅是在这个特定事件中。

【讨论】:

  • Async-Await 不应该意味着您不必进行调用吗?
  • @Ringil 是的。可悲的是,包括这个回答者在内的太多人实际上并不理解这一点,并被困在过去,并坚持使用极其过时和劣质的工具来完成这项任务。
  • 这并不能解释为什么第二个 sn-p 工作得很好(因为这些工具应该是这样使用的;这个答案不是。)使用Progress 的全部意义在于这样您就不必显式调用。
  • @MichałKędrzyński await 肯定与调度员有关。当您await 时,当前同步上下文被捕获,并且在 WPF 应用程序中将成为 Dispatcher 的包装器,这将导致将继续发布到该同步上下文,该同步上下文会将其发布到调度程序.不需要自定义等待者来执行任何此操作。另请注意,OP 似乎使用的是 winform 应用程序,而不是 WPF 应用程序,因此没有调度程序。幸运的是 awaitProgress 不依赖于这些细节,不像你的代码。
  • @vidalsasoon 这一切都取决于具体情况,但在绝大多数情况下,不会。如果您的 API 表示不提供 Task 的异步操作,则为您留下事件/回调/等。那么您首先需要将其转换为Task(具体取决于它在做什么),以便您可以从UI线程中await它。在某些非常不可能的情况下,这样做可能不切实际,因此您需要捕获 UI 的同步上下文并使用它来启动将在 UI 上下文中运行的新线程。
猜你喜欢
  • 2017-04-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
  • 2018-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多