Запуск асинхронного процесса и ожидание его завершения
Я новичок в модели потока в.net. Что бы вы использовали для:
- запустить процесс, который обрабатывает файл (process.StartInfo.FileName = fileName;)
- подождите, пока пользователь не закроет процесс ИЛИ не покинет поток через некоторое время
- если пользователь закрыл процесс, удалите файл
Запуск и ожидание процесса должны выполняться в другом потоке, нежели основной поток, поскольку эта операция не должна влиять на приложение.
Пример:
Мое приложение создает HTML-отчет. Пользователь может щелкнуть правой кнопкой мыши где-нибудь и сказать "Просмотреть отчет" - теперь я извлекаю содержимое отчета во временный файл и запускаю процесс, который обрабатывает HTML-файлы, то есть браузер по умолчанию. Проблема в том, что я не могу очистить, т.е. удалить временный файл.
6 ответов
"и ожидание должно быть асинхронным" - я не пытаюсь быть смешным, но разве это не противоречие в терминах? Тем не менее, так как вы начинаете Process
, Exited
событие может помочь:
ProcessStartInfo startInfo = null;
Process process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
process.Exited += delegate {/* clean up*/};
Если вы действительно хотите подождать (время ожидания и т. Д.), То:
if(process.WaitForExit(timeout)) {
// user exited
} else {
// timeout (perhaps process.Kill();)
}
Для ожидания асинхронного, возможно, просто использовать другой поток?
ThreadPool.QueueUserWorkItem(delegate {
Process process = Process.Start(startInfo);
if(process.WaitForExit(timeout)) {
// user exited
} else {
// timeout
}
});
Добавление продвинутой альтернативы этому старому вопросу. Если вы хотите дождаться завершения процесса, не блокируя какой-либо поток и по-прежнему поддерживая таймауты, попробуйте следующее:
public static Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
{
ManualResetEvent processWaitObject = new ManualResetEvent(false);
processWaitObject.SafeWaitHandle = new SafeWaitHandle(process.Handle, false);
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
RegisteredWaitHandle registeredProcessWaitHandle = null;
registeredProcessWaitHandle = ThreadPool.RegisterWaitForSingleObject(
processWaitObject,
delegate(object state, bool timedOut)
{
if (!timedOut)
{
registeredProcessWaitHandle.Unregister(null);
}
processWaitObject.Dispose();
tcs.SetResult(!timedOut);
},
null /* state */,
timeout,
true /* executeOnlyOnce */);
return tcs.Task;
}
Опять же, преимущество этого подхода по сравнению с принятым ответом состоит в том, что вы не блокируете какие-либо потоки, что снижает нагрузку на ваше приложение.
Попробуйте следующий код.
public void KickOffProcess(string filePath) {
var proc = Process.Start(filePath);
ThreadPool.QueueUserWorkItem(new WaitCallBack(WaitForProc), proc);
}
private void WaitForProc(object obj) {
var proc = (Process)obj;
proc.WaitForExit();
// Do the file deletion here
}
В .NET 5 представлен новый APIProcess.WaitForExitAsync
, что позволяет асинхронно ожидать завершения процесса. Он предлагает ту же функциональность, что и существующийProcess.WaitForExit
, с той лишь разницей, что ожидание асинхронно, поэтому оно не блокирует вызывающий поток.
Пример использования:
private async void button1_Click(object sender, EventArgs e)
{
string filePath = Path.Combine
(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Guid.NewGuid().ToString() + ".txt"
);
File.WriteAllText(filePath, "Hello World!");
try
{
using Process process = new();
process.StartInfo.FileName = "Notepad.exe";
process.StartInfo.Arguments = filePath;
process.Start();
await process.WaitForExitAsync();
}
finally
{
File.Delete(filePath);
}
MessageBox.Show("Done!");
}
В приведенном выше примере пользовательский интерфейс остается отзывчивым, пока пользователь взаимодействует с открытым файлом. Поток пользовательского интерфейса будет заблокирован, еслиWaitForExit
вместо этого использовался.
Вы можете использовать Exited
событие в классе Process
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "notepad.exe";
Process process = Process.Start(info);
process.Exited += new EventHandler(process_Exited);
Console.Read();
и в этом случае вы можете справиться с операциями, которые вы упомянули
Я бы, вероятно, не использовал отдельный процесс для открытия файла. Вместо этого я, вероятно, использовал бы фоновый поток (если бы я думал, что операция займет много времени и возможно заблокирует поток пользовательского интерфейса).
private delegate void FileOpenDelegate(string filename);
public void OpenFile(string filename)
{
FileOpenDelegate fileOpenDelegate = OpenFileAsync;
AsyncCallback callback = AsyncCompleteMethod;
fileOpenDelegate.BeginInvoke(filename, callback, state);
}
private void OpenFileAsync(string filename)
{
// file opening code here, and then do whatever with the file
}
Конечно, это не очень хороший рабочий пример (он ничего не возвращает), и я не показал, как обновляется пользовательский интерфейс (вы должны использовать BeginInvoke на уровне пользовательского интерфейса, потому что фоновый поток не может обновить поток пользовательского интерфейса). Но этот подход, как правило, заключается в том, как я выполняю асинхронные операции в.Net.