C# 在 async/await 代码中实现调度程序

C# implementing dispatcher in async/await code

我有一个 wpf 应用程序,其中一个功能是复制文件。它运行正常,但有时会冻结 UI,因此我正在尝试向其添加 async/await 功能。

这是代码:

if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            string[] files = filePickedTextBox.Text.Split('\n');
            await Task.Run(() =>
                {
                    foreach (var file in files)
                    {
                        try
                        {
                            AddValueLoadingBar(20, true);
                            File.Copy(file, Path.Combine(dialog.SelectedPath, Path.GetFileName(file)));
                            newDirTextBox.Text = Path.Combine(dialog.SelectedPath, Path.GetFileName(file));
                        }

                        catch(Exception ex)
                        {
                            newDirTextBox.Text = ex.ToString();
                        }

                        finally
                        {
                            AddValueLoadingBar(100, true);
                        }
                    }                    
                });

我曾尝试在多个位置添加 dispatcher.invoke,但在需要与文本框对话时总是收到一个错误,提示它正在请求来自不同线程的请求。

我已经做了好几个小时了,并且在网上搜索了许多 Whosebug 线程和其他文档,但我不知道如何实现它。

我也知道我的进度条实施很糟糕..但我现在并不担心,因为我无法将其设置为 运行 而不首先冻结。

任何帮助都会很棒。

感谢

这里是你使用 System.Windows.Threading.Dispatcher class.

的地方

您知道对话框构造函数将在主线程上 运行。所以在dialog创建的时候,你把主线程的dispatcher记录为一个成员变量。

然后,在任务线程内,确保通过此调度程序将任何更新更改调度到 windows 线程。

using System.Windows;

class MyTestDialog
{
    readonly Dispatcher _uiThreadDispatcher = Dispatcher.CurrentDispatcher;

    public async Task DoSomethingInBackround()
    {
        await Task.Run(
             () => 
             {
                //
                // Do a bunch of stuff here
                //
                _uiThreadDispatcher.BeginInvoke(
                     new Action(
                         () => newDirTextBox.Text = "Some progress text";
                     )
                );
                // 
                // Do some more stuff
                // 
             }
        );
    }
}

在您的 Task.Run 中,您无法访问或更新任何 UI 元素,除非调用返回到 UI 线程的回调。你的每一个陈述似乎都在与 UI 互动。在将艰苦的工作推入任务之前,您需要将所有 UI 工作提取到代码中。

现在,我建议使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive.Windows.Threading(对于 WPF 位)并添加 using System.Reactive.Linq; - 然后您可以这样做:

IDisposable subscription =
    files
        .Select(f => new
        {
            source = f,
            destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(f))
        })
        .ToObservable()
        .SelectMany(x => Observable.Start(() =>
        {
            File.Copy(x.source, x.destination);
            return x.destination;
        }))
        .Select((d, n) => new { destination = d, progress = 100 * (n + 1) / files.Length })
        .ObserveOnDispatcher()
        .Subscribe(
            x =>
            {
                AddValueLoadingBar(x.progress, true);
                newDirTextBox.Text = x.destination; 
            },
            ex => newDirTextBox.Text = ex.ToString(),
            () => AddValueLoadingBar(100, true));

这完成了将实际文件复制发送到后台线程所需的一切,但仍将所有更新编组到 UI。