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

在C#中调用外部exe时如何使UI响应?,c#,wpf,C#,Wpf,我正在编写一个WPF程序,它使用exe文件从硬件捕获数据。完成一个电话大约需要2秒钟。我已经使用这个exe多次(>500)背靠背使用不同的参数。在下一次调用之前,我必须等待每个进程完成。因为,我不能同时运行多个exe。硬件不支持它。同时,我还显示了UI的更新,并使UI随时响应取消任务 我对如何使用async await、Dispatcher.BeginInvoke或Task.run来解决我的问题感到困惑。任何帮助或想法都将受到高度赞赏 ObservableCollection <

我正在编写一个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
}
observetecollectionfileNames=newobservetecollection();
//文件名用于在UI中显示列表框中的文件名。一定是这样
//实时更新。
私有void BtnStartCapture_单击(对象发送方,路由目标){

对于(inti=1;i,下面使用Invoke和WaitForExit方法来实现所需的结果

Edit我编辑了下面的代码,以便它支持对启动进程的线程的“背靠背”调用

    static readonly object _object = new object();
    public int iProcCount = 0;
    public void StartExe()
    {
        System.Diagnostics.Process proc;
        lock (_object) // Because WaitForExit is inside the lock all other 
                       // instances of this thread must wait before the 
                       // previous thread has finished
        {
            proc = System.Diagnostics.Process.Start(strExePath);
            proc.WaitForExit(); // This is not on the UI thread so it will not block the UI thread
        }
        this.Invoke(new Action(() => UpdateGUI(this, "Finished Proc " + iProcCount)));
        iProcCount++;
    }

    public void UpdateGUI(Form theGui, string msg)
    { // This will wil update the GUI when the GUI thread is not busy
        lblGUIUpdate.Text = msg;
    }

    private void Button_Click(object sender, EventArgs e)
    {
        Thread th = new Thread(StartExe);
        th.Start(); // After this, the GUI thread will not block
    }  

这里有三个问题:

  • 如何等待进程退出而不阻塞UI线程
  • 如何防止按钮在完成之前再次被单击
  • 如何取消
  • 对于一个单堆栈溢出问题来说,这是一个很大的问题;将来,请一次只问一个问题

    如何等待进程退出而不阻塞UI线程

    您可以使用
    TaskCompletionSource
    钩住退出的

    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;
    }
    
    请注意。通常,您应该避免
    async void

    如何防止按钮在完成之前再次被单击

    一种常见模式是在按钮运行时禁用按钮,例如:

    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;
      }
    }
    

    您希望对这个外部应用程序进行异步调用…但您总是希望在继续之前等待它的完成?抱歉,我有点困惑,因为您当前的实现似乎为您完成了这项工作-那么您希望在合并异步调用时,结果如何表现出不同的行为?请明确:I喜欢你的问题,但我不理解你想要做的更改及其原因。我必须等待进程在下一次调用之前完成,因为我不能同时运行多个exe实例并显示UI的更新。在我的curernt实现中,UI冻结,并在所有exe调用结束后显示更新。我不知道如何操作要完成它。您正在将UI线程发送到CaptureFrame及其工作的dng,这样UI将没有响应,这样您就可以使用线程来完成该工作。我会检查一下wt我可以做什么。
    WaitForExit
    做什么?@Fildor Good thing
    Process
    会以一种方式通知您它完成时,以便您可以o完成时可能需要的任何工作。就像任务一样。在StartExe完成之前,你仍然可以按两次按钮,不是吗?如果“按钮”是指日期时间选择器,那么是的。我的意思是,你可以在前一次执行完成之前再次启动该过程。问题是“我必须等待每个进程在下一次调用之前完成”。但是,如果您想要得到这个结果,只需在所有“三个”进程启动之后创建一个单独的进程变量并为每个进程调用WaitForExit。所以?如果您连续两次按start按钮,则会有两个线程启动执行外部执行的进程。。。
    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;
      }
    }
    
    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();
    }