c# - 如何在 C# 中调用外部 exe 时使 UI 响应?

标签 c# wpf

我正在编写一个 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 个问题:

  1. 如何在不阻塞 UI 线程的情况下等待进程退出?
  2. 如何防止按钮在完成之前被再次点击?
  3. 如何取消?

这是单个 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;
}

但是,此代码有一些注意事项:

  1. 您必须设置 Process.EnableRaisingEventstrue在流程开始之前。
  2. 您必须调用 WaitForExitedAsync在流程开始之前。
  3. 何时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/

相关文章:

c# - 在 DataGridView 中拖放多列

c# - 正则表达式替换 (C#)

c# - WindowsBase.dll 中发生类型为 'System.InvalidCastException' 的第一次机会异常

c# - 使用触发器 WPF MVVM 更改图像

c# - 使用 Application.Current.Dispatcher 进行单元测试

wpf - 闪烁动画 WPF

wpf - WPF RichTextBox用行号创建编辑器

c# - 当 Gridview 在更新面板内时,模态弹出窗口无法正常工作

c# - 我应该使用哪个 header ? XMLHTTPRequest header 还是 SOAP header ?

c# - 从 View 组件中获取 session ID