我正在编写一个WPF程序,该程序使用exe文件从硬件捕获数据。完成一个通话大约需要2秒钟。我已经多次使用此exe(> 500)并使用了不同的参数。我必须等待每个进程完成之后才能进行下一次调用。因为我不能同时运行多个exe。硬件不支持它。同时,我已经显示了UI的更新,并且使UI随时响应以随时取消任务。
对于如何使用异步等待,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
}
答案 0 :(得分:1)
您在这里有3个问题:
对于单个堆栈溢出问题,这是一个 很多 ;将来,请一次只问一个问题。
如何等待进程退出而不阻塞UI线程?
您可以使用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
。
如何防止在完成按钮之前再次单击它?
一种常见的模式是在按钮运行时禁用它,例如:
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;
}
}
如何取消?
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();
}
答案 1 :(得分:0)
以下内容使用Invoke和WaitForExit方法来获得所需的结果。
编辑我编辑了以下代码,因此它将支持对启动该过程的线程的“背对背”调用。
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
}
答案 2 :(得分:-1)
在按钮上创建一个新线程,以便主线程空闲
private void Button_Click()
{
new Thread(() => ProxyMethod()).Start();
}
对于每个外部方法,调用创建一个新线程,我将其限制为150个以内,您可以在系统的资源上对其进行更改
void ProxyMethod()
{
try
{
for (int i = 1; i <= 1000; i++)
{
Thread th = new Thread(() =>
{
try
{//New thread for every iteration
var result = CaptureFrame(1, 1);
if (result == true)
{
//File.add
}
}
catch
{
}
RunningThreads.Remove(Thread.CurrentThread);
});
RunningThreads.Add(th);
th.Start();
while(RunningThreads.Count>150)
{
Thread.Sleep(100);
}
}
}
catch
{
}
}
internal bool CaptureFrame(int boardId, int frameNo)
{
Thread.Sleep(5000);
return true;
}