我正在编写一个 WPF 程序,它使用一个 exe 文件从硬件中捕获数据。完成一次通话大约需要 2 秒。我已经多次使用这个 exe(> 500 次)使用不同的参数。我必须等待每个进程在下一次调用之前完成。因为,我不能同时运行多个 exe。硬件不支持。与此同时,我展示了 UI 的更新,并保持 UI 随时响应取消任务。
我对使用 async-await、Dispatcher.BeginInvoke 或 Task.run 来解决我的问题的目的和方式感到困惑。任何帮助或想法将不胜感激。
ObservableCollection < String > fileNames = new ObservableCollection < string > ();
//fileNames is used to show the file names a ListBox in UI. It have to be
// updated in real time.
private void BtnStartCapture_Click(object sender, RoutedEventArgs e) {
for (int i = 1; i <= CaptureSettings.NoOfFrames; i++) {
String currentFile;
if (CaptureSettings.CkBoard1 == true) {
currentFile = CaptureSettings.CaptureFrame(1, i);
fileNames.Add(currentFile);
}
if (CaptureSettings.CkBoard2 == true) {
currentFile = CaptureSettings.CaptureFrame(2, i);
fileNames.Add(currentFile);
}
}
}
internal String CaptureFrame(int boardId, int frameNo) {
string arg = createCommandLIneArgument(boardId, frameNo);
try {
ProcessStartInfo pInfo1 = new ProcessStartInfo {
FileName = "GrabberTest1.exe",
Arguments = arg,
WindowStyle = ProcessWindowStyle.Hidden
};
var process1 = Process.Start(pInfo1);
process1.WaitForExit();
return Path.GetFileNameWithoutExtension(arg);
} catch(Exception) {
return "Failed " + Path.GetFileNameWithoutExtension(arg);
}
}
private void BtnCancelCapture_Click(object sender, RoutedEventArgs e) {
//to do
}
最佳答案
你在这里有 3 个问题:
- 如何在不阻塞 UI 线程的情况下等待进程退出?
- 如何防止按钮在完成之前被再次点击?
- 如何取消?
这是单个 Stack Overflow 问题的很多;以后请一次只问一个问题。
How do I wait for a process to exit without blocking the UI thread?
你可以使用 TaskCompletionSource<T>
钩入 Exited
因此:
public static Task<int> WaitForExitedAsync(this Process process)
{
var tcs = new TaskCompletionSource<int>();
EventHandler handler = null;
handler = (_, __) =>
{
process.Exited -= handler;
tcs.TrySetResult(process.ExitCode);
};
process.Exited += handler;
return tcs.Task;
}
但是,此代码有一些注意事项:
- 您必须设置
Process.EnableRaisingEvents
至true
在流程开始之前。 - 您必须调用
WaitForExitedAsync
在流程开始之前。 - 何时
Exited
被引发,这并不意味着 stdout/stderr 流已完成。刷新这些流的唯一方法是调用WaitForExit
(进程退出后)。不太直观。
为简单起见,您可能只想调用 WaitForExit
而不是在后台线程上。这将使用额外的不必要线程,但对于 GUI 应用程序,这并不重要。
在您的代码中,您可以推送 CaptureFrame
关闭后台线程:
private async void BtnStartCapture_Click(object sender, RoutedEventArgs e)
{
for (int i = 1; i <= CaptureSettings.NoOfFrames; i++)
{
String currentFile;
if (CaptureSettings.CkBoard1 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(1, i));
fileNames.Add(currentFile);
}
if (CaptureSettings.CkBoard2 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(2, i));
fileNames.Add(currentFile);
}
}
}
请注意 async void
is used here only because this is an event handler .通常,你应该避免 async void
.
How do I prevent a button from being clicked again until it finishes?
一种常见的模式是在按钮运行时将其禁用,例如:
private async void BtnStartCapture_Click(object sender, RoutedEventArgs e)
{
BtnStartCapture.Enabled = false;
try
{
for (int i = 1; i <= CaptureSettings.NoOfFrames; i++)
{
String currentFile;
if (CaptureSettings.CkBoard1 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(1, i));
fileNames.Add(currentFile);
}
if (CaptureSettings.CkBoard2 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(2, i));
fileNames.Add(currentFile);
}
}
}
finally
{
BtnStartCapture.Enabled = true;
}
}
How do I cancel?
Cancellation in .NET follows a standard pattern .被取消的代码观察到 CancellationToken
, 可以从 CancellationTokenSource
中设置.每个CancellationTokenSource
是一种取消操作的方式,但是只能使用一次。所以在这种情况下,您需要一个新的 CancellationTokenSource
每次操作开始。
您可以将取消请求解释为外部进程的终止请求,但在您的情况下,我认为将取消请求解释为“让当前外部进程完成;只是不要捕捉下一帧”。我认为这更好,因为外部进程与硬件设备通信(我们不想进入意外状态),而且速度相当快。
private CancellationTokenSource _cts;
private async void BtnStartCapture_Click(object sender, RoutedEventArgs e)
{
_cts = new CancellationTokenSource();
var token = _cts.Token;
BtnStartCapture.Enabled = false;
BtnCancelCapture.Enabled = true;
try
{
for (int i = 1; i <= CaptureSettings.NoOfFrames; i++)
{
token.ThrowIfCancellationRequested();
String currentFile;
if (CaptureSettings.CkBoard1 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(1, i));
fileNames.Add(currentFile);
}
if (CaptureSettings.CkBoard2 == true)
{
currentFile = await Task.Run(() => CaptureSettings.CaptureFrame(2, i));
fileNames.Add(currentFile);
}
}
}
catch (OperationCanceledException)
{
// TODO: decide what to do here - clear fileNames? Display a message? Nothing?
}
finally
{
BtnStartCapture.Enabled = true;
BtnCancelCapture.Enabled = false;
}
}
private void BtnCancelCapture_Click(object sender, RoutedEventArgs e)
{
_cts.Cancel();
}
关于c# - 如何在 C# 中调用外部 exe 时使 UI 响应?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56808100/