簡體   English   中英

異步進程啟動並等待它完成

[英]Async process start and wait for it to finish

我是 .NET 中的線程 model 的新手。 你會用什么來:

  1. 啟動處理文件的進程(process.StartInfo.FileName = fileName;)
  2. 等待用戶關閉進程或在一段時間后放棄線程。
  3. 如果用戶關閉了進程,則刪除該文件。

啟動進程和等待應該在與主線程不同的線程上完成,因為這個操作不應該影響應用程序。

例子:

我的應用程序生成 html 報告。 用戶可以右鍵單擊某處並說“查看報告”——現在我在一個臨時文件中檢索報告內容並啟動處理 html 文件的進程,即默認瀏覽器。 問題是我無法清理,即刪除臨時文件。

“等待必須是異步的” - 我不是想搞笑,但這不是矛盾嗎? 但是,由於您正在啟動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 引入了新的 API Process.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!");
}

在上面的示例中,當用戶與打開的文件交互時,UI 保持響應。 如果改為使用WaitForExit ,則 UI 線程將被阻塞。

我可能不會使用單獨的進程來打開文件。 相反,我可能會使用后台線程(如果我認為該操作需要很長時間並且可能會阻塞 UI 線程)。

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
}

當然,這不是一個好的工作示例(它不返回任何內容),並且我沒有展示 UI 是如何更新的(您必須在 UI 級別使用 BeginInvoke,因為后台線程無法更新 UI 線程)。 但是這種方法一般是我在.Net中如何處理異步操作的。

您可以在進程 class 中使用Exited事件

ProcessStartInfo info = new ProcessStartInfo();

info.FileName = "notepad.exe";
Process process = Process.Start(info);

process.Exited += new EventHandler(process_Exited);
Console.Read();

在那種情況下,您可以處理您提到的操作

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM