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。
我有一个 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。