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