У вас есть 3 вопроса здесь:
- Как мне ждать завершения процесса, не блокируя поток пользовательского интерфейса?
- Как предотвратить повторное нажатие кнопки до ее завершения?
- Как мне отменить?
Это лот для одного вопроса переполнения стека; в будущем, пожалуйста, задавайте только один вопрос за раз.
Как мне ждать завершения процесса, не блокируя поток пользовательского интерфейса?
Вы можете использовать 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
в фоновом потоке. Это будет использовать дополнительный ненужный поток, но для приложения с графическим интерфейсом это не критично.
В своем коде вы можете отправить 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
используется здесь только , потому что это обработчик событий . Обычно вам следует избегать 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;
}
}
Как мне отменить?
Отмена в .NET следует стандартному шаблону . Отменяемый код соответствует 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();
}