如何等到事件在c#中处理

Posted

技术标签:

【中文标题】如何等到事件在c#中处理【英文标题】:How to wait until event is handled in 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 如果您想坚持事件模式,请查看ManualResetEvent 或ManualResetEventSlim。不过,您也可以切换到基于任务的异步 (TAP)。 @LouisGo 是的。我的错误,我使用 Kill() @LouisGo 我不明白我可以在这里等待什么。我不调用异步方法。我杀死了一个进程,操作系统发出该进程退出的信号,然后调用处理程序 @Alexander 抱歉,Fildor 的建议更适合您的情况,我将删除我的评论以防止混淆。 【参考方案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 线程安全递增的方法。

以上是关于如何等到事件在c#中处理的主要内容,如果未能解决你的问题,请参考以下文章

了解 C# 中的事件和事件处理程序

如何使事件处理程序异步运行?

C#按钮click事件是如何触发的

如何在 2 个独立的 C# 程序之间进行事件驱动编程?

C#如何在 Windows 窗体应用程序中使用事件

如何从 C++ 代码引发事件并在另一个进程的 C# 代码中调用处理程序?