【问题标题】:How to wait until event is handled in c#如何等到事件在c#中处理
【发布时间】:2020-07-05 18:34:31
【问题描述】:

假设我们有这样的代码:

public class MyProcess
{
    private Process _process;
    public event EventHandler OnFinish;
    public MyProcess(string pathToExe)
    {
         _process = new Process();
         _process.StartInfo.FileName = pathToExe;
    }
    public int Start()
    {
         _process.Exited += _processExited;
         _process.Start();
         return _process.Id;
    }
    public void Stop()
    {
         _process.Stop();
    }
    private void _processExited(object sender, EventArgs e)
    {
         OnFinish?.Invoke();
    }
}

public class MyProgram
{
    private static int stoppedProcs = 0;

    public static void Main(string[] args)
    {
         var proc = new MyProcess("foo.exe");
         proc.OnFinish += proc_OnFinish;
         proc.Start();
         proc.Stop();
         //Wait here to display 1 instead of 0
         Console.WriteLine(stoppedProcs);
    }

    private static void proc_OnFinish(object sender, EventArgs e)
    {
         stoppedProcs++;
    }
}

我创建并启动进程。然后我想停止它并仅在处理完事件后继续。问题是我不会自己调用事件,因为它是由操作系统完成的。如何理解事件处理程序已完成?

【问题讨论】:

  • 你的ProcessSystem.Diagnostics.Process 吗?我在System.Diagnostics.Process 中找不到Stop
  • 如果您想坚持事件模式,请查看ManualResetEventManualResetEventSlim。不过,您也可以切换到基于任务的异步 (TAP)。
  • @LouisGo 是的。我的错误,我使用 Kill()
  • @LouisGo 我不明白我可以在这里等待什么。我不调用异步方法。我杀死了一个进程,操作系统发出该进程退出的信号,然后调用处理程序
  • @Alexander 抱歉,Fildor 的建议更适合您的情况,我将删除我的评论以防止混淆。

标签: c# .net multithreading events


【解决方案1】:

假设您想同步等待(阻塞当前线程),只需通过您的类公开Process.WaitForExit 方法。

public void WaitForExit()
{
    _process.WaitForExit();
}

【讨论】:

    【解决方案2】:

    扩展提到 Taskawait 东西的答案

    如果您想将等待变成异步流,您可以将Exited 事件与TaskCompletionSource 结合起来,如下所示:

    public static class ProcessExtension
    {
        public static Task WaitForExitAsync(this Process proc, Action callback)
        {
            return new WaitForExitExt(proc, callback).WaitTask;
        }
    
        class WaitForExitExt
        {
            private TaskCompletionSource<int> _tcs;
            private Action _callback;
    
            public WaitForExitExt(Process proc, Action callback)
            {
                _callback = callback;
                proc.EnableRaisingEvents = true;
    
                _tcs = new TaskCompletionSource<int>();
    
                if (proc.HasExited)
                {
                    _tcs.TrySetResult(proc.ExitCode);
                }
                else
                {
                    proc.Exited += OnProcessExited;
    
                    // Process already exited by the time the handler was attached.
                    if (proc.HasExited)
                    {
                        proc.Exited -= OnProcessExited;
                        _tcs.TrySetResult(proc.ExitCode);
                    }
                }
            }
    
            public Task WaitTask => _tcs.Task;
    
            private void OnProcessExited(object sender, EventArgs e)
            {
                var proc = (Process)sender;
                proc.Exited -= OnProcessExited;
    
                _callback();
    
                _tcs.TrySetResult(proc.ExitCode);
            }
        }
    }
    

    上述扩展能够在进程退出后运行callback,并且保证在任务完成之前运行callback,因此您可以在代码中使用它:

    var process = new Process();
    process.StartInfo.FileName = "notepad.exe";
    process.EnableRaisingEvents = true;
    process.Start();
    
    var task = process.WaitForExitAsync(() => Console.WriteLine("Exited"));
    process.Kill();
    await task;
    
    Console.WriteLine("Task finished");
    

    输出是:

    Exited
    Task finished
    

    【讨论】:

      【解决方案3】:

      感谢大家回答我的问题。 通过@Fildor 建议使用ManualResetEvent,问题很容易解决

      public class MyProgram
      {
      private static int stoppedProcs = 0;
      private static ManualResetEvent mre = new ManualResetEvent(false);
      public static void Main(string[] args)
      {
           var proc = new MyProcess("foo.exe");
           proc.OnFinish += proc_OnFinish;
           proc.Start();
           proc.Stop();
           //Wait here to display 1 instead of 0
           mre.WaitOne();
           mre.Reset();
           Console.WriteLine(stoppedProcs);
      }
      
      private static void proc_OnFinish(object sender, EventArgs e)
      {
           stoppedProcs++;
           mre.Set();
      }
      }
      

      【讨论】:

      • 这个解决方案可能很简单,但它是否健壮?如果在mre.WaitOne() 行之前调用proc_OnFinish 处理程序会发生什么?是否可以保证这永远不会发生?
      • @TheodorZoulias,好问题。据我了解,这无关紧要。如果处理程序在mre.WaitOne() 之前调用并调用mre.Set() 则不会阻塞主线程。
      • 你说得对,我忘记了!所以你没有竞争条件,前提是proc_OnFinish 只会运行一次。 stoppedProcs++ 行表明情况可能并非如此!顺便看看Interlocked.Increment 线程安全递增的方法。
      猜你喜欢
      • 2021-01-23
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 2013-07-04
      • 2017-02-04
      • 1970-01-01
      • 1970-01-01
      • 2012-06-11
      相关资源
      最近更新 更多