C# 如何让UI线程在等待信号量的同时处理其他调度程序请求?(就像MessageBox.Show本机所做的那样)

C# 如何让UI线程在等待信号量的同时处理其他调度程序请求?(就像MessageBox.Show本机所做的那样),c#,.net-4.0,error-handling,parallel-processing,semaphore,C#,.net 4.0,Error Handling,Parallel Processing,Semaphore,通常,当UI线程调用类似于MessageBox.Show()的东西时,当前代码执行不会继续,直到用户单击OK,但程序将继续运行UI线程上分派的其他代码 在中,我遇到了一个问题,即一次调用UI线程上调度的委托太多。我想在继续执行之前在某些点暂停 在我的新错误处理程序中,我使用信号量来确保一次处理的错误不超过一个。我发送一个MessageBox来提醒用户,当他们单击“OK”时,我释放信号量,允许处理下一个错误 问题是,它的行为并不像预期的那样。如果同时发生对HandleError的两个已调度调用,则

通常,当UI线程调用类似于
MessageBox.Show()
的东西时,当前代码执行不会继续,直到用户单击OK,但程序将继续运行UI线程上分派的其他代码

在中,我遇到了一个问题,即一次调用UI线程上调度的委托太多。我想在继续执行之前在某些点暂停

在我的新错误处理程序中,我使用信号量来确保一次处理的错误不超过一个。我发送一个MessageBox来提醒用户,当他们单击“OK”时,我释放信号量,允许处理下一个错误

问题是,它的行为并不像预期的那样。如果同时发生对HandleError的两个已调度调用,则第一个将调度对MessageBox.Show的调用,第二个将阻止UI线程。奇怪的是,对
MessageBox.Show()
的调度调用从未执行过—整个应用程序只是挂起—因此当用户单击“确定”时应该释放的信号量被永久锁定。这个解决方案缺少什么

private static ConcurrentDictionary<Exception, DateTime> QueuedErrors = new ConcurrentDictionary<Exception, DateTime>();
private static Semaphore Lock_HandleError = new Semaphore(1, 1); //Only one Error can be processed at a time
private static void ErrorHandled(Exception ex)
{
    DateTime value;
    QueuedErrors.TryRemove(ex, out value);
    Lock_HandleError.Release();
}

private static bool ExceptionHandlingTerminated = false;
public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
{
    if( ExceptionHandlingTerminated || App.Current == null) return;
    QueuedErrors.TryAdd(ex, DateTime.Now); //Thread safe tracking of how many simultaneous errors are being thrown

    Lock_HandleError.WaitOne(); //This will ensure only one error is processed at a time.

    if( ExceptionHandlingTerminated || App.Current == null )
    {
        ErrorHandled(ex);
        return;
    }

    try
    {
        if( QueuedErrors.Count > 10 )
        {
            ExceptionHandlingTerminated = true;
            throw new Exception("Too many simultaneous errors have been thrown in the background.");
        }

        if( Thread.CurrentThread != Dispatcher.CurrentDispatcher.Thread )
        {
            //We're not on the UI thread, we must dispatch this call.
            ((App)App.Current).Dispatcher.BeginInvoke((Action<Exception, string, bool, bool>)
                delegate(Exception _ex, string _extraInfo, bool _showMsgBox, bool _resetApplication)
                {
                    ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
                    HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication);
                }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication });
            return;
        }

        if( showMsgBox )
        {
            //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
            //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke because dispatcher processing is suspended in such cases.
            Dispatcher.CurrentDispatcher.BeginInvoke((Action<Exception, String>)delegate(Exception _ex, String _ErrMessage)
            {
                MessageBox.Show(_ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error);
                ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
            }, DispatcherPriority.Background, new object[]{ ex, extraInfo });
        }
        else
        {
            ErrorHandled(ex);
        }
    }
    catch( Exception terminatingError )
    {
        ExceptionHandlingTerminated = true;
        Dispatcher.CurrentDispatcher.BeginInvoke((Action<String>)delegate(String _fatalMessage)
        {
            MessageBox.Show(_fatalMessage, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Stop);
            if( App.Current != null ) App.Current.Shutdown(1);
        }, DispatcherPriority.Background, new object[] { fatalMessage });
        ErrorHandled(ex); //Release the semaphore taken by this HandleError call which will allow all other queued HandleError calls to continue and check the ExceptionHandlingTerminated flag.
    }
}
私有静态ConcurrentDictionary QueuedErrors=new ConcurrentDictionary();
私有静态信号量锁\u HandleError=新信号量(1,1)//一次只能处理一个错误
私有静态void ErrorHandled(异常ex)
{
日期时间值;
TryRemove(例如,out值);
锁紧手柄错误。释放();
}
私有静态bool异常handlingterminated=false;
public static void HandleError(异常ex,字符串extraInfo=“”,bool showMsgBox=true,bool resetApplication=true)
{
if(ExceptionHandlingTerminated | | App.Current==null)返回;
QueuedErrors.TryAdd(ex,DateTime.Now);//线程安全跟踪同时抛出的错误数量
Lock_HandleError.WaitOne();//这将确保一次只处理一个错误。
if(异常处理终止| |应用程序当前==null)
{
错误处理(ex);
返回;
}
尝试
{
如果(队列错误计数>10)
{
异常处理终止=真;
抛出新异常(“后台同时抛出的错误太多。”);
}
if(Thread.CurrentThread!=Dispatcher.CurrentDispatcher.Thread)
{
//我们不在UI线程上,必须调度此调用。
((应用程序)应用程序当前).Dispatcher.BeginInvoke((操作)
委托(异常例外、字符串外部信息、bool\u showMsgBox、bool\u重置应用程序)
{
ErrorHandled(_ex);//释放生成HandleError调用所获取的信号量
HandleError(_ex,_extraInfo,_showMsgBox,_resetApplication);
},DispatcherPriority.Background,新对象[]{ex,extraInfo,showMsgBox,resetApplication});
返回;
}
if(showMsgBox)
{
//如果UI正在处理可视树事件(如IsVisibleChanged),则在显示MessageBox时会引发异常,如下所述:http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
//解决方案是分派MessageBox并将其排队。我们必须使用BeginInvoke,因为在这种情况下,分派器处理将挂起。
Dispatcher.CurrentDispatcher.BeginInvoke((操作)委托(异常,字符串错误消息)
{
MessageBox.Show(_ErrMessage,“MUS应用程序错误”,MessageBoxButton.OK,MessageBoxImage.Error);
ErrorHandled(_ex);//释放生成HandleError调用所获取的信号量
},DispatcherPriority.Background,新对象[]{ex,extraInfo});
}
其他的
{
错误处理(ex);
}
}
捕获(异常终止错误)
{
异常处理终止=真;
Dispatcher.CurrentDispatcher.BeginInvoke((操作)委托(字符串\u fatalMessage)
{
MessageBox.Show(_fatalMessage,“致命错误”,MessageBoxButton.OK,MessageBoxImage.Stop);
如果(App.Current!=null)App.Current.Shutdown(1);
},DispatcherPriority.Background,新对象[]{fatalMessage});
ErrorHandled(ex);//释放此HandleError调用使用的信号量,这将允许所有其他排队的HandleError调用继续并检查ExceptionHandlingTerminated标志。
}
}

不要担心丢失的消息字符串,为了使模式更清晰,我删去了很多细节。

假设您要查找的行为是每个消息框依次等待,直到前一个消息框被清除,您需要这样的模式:

  • 事件源将消息排入阻塞队列
  • 事件源调用后台线程上的委托以“处理队列”
  • “processthequeue”委托接受一个锁(正如您所做的那样),对消息进行出列,并(同步地)调用UI线程以显示消息。然后它循环,做同样的事情,直到队列是emtpy
  • 这样的话(未经测试的代码):

    private static ConcurrentQueue QueuedErrors=new ConcurrentQueue();
    私有静态对象锁_HandleError=new Object();
    public static void HandleError(异常ex,字符串extraInfo=“”,bool showMsgBox=true,bool resetApplication=true)
    {
    排队(新元组(例如,DateTime.Now));
    ThreadPool.QueueUserWorkItem(()=>((应用)App.Current.Dispatcher.Invoke((操作)
    () => {
    锁(锁柄错误)
    元组currentEx;
    while(QueuedErrors.TryDequeue(out currentEx))
    MessageBox.Show(
    currentEx.Item1,//异常
    
    private static ConcurrentQueue<Tuple<Exception, DateTime>> QueuedErrors = new ConcurrentQueue<Tuple<Exception, DateTime>>();
    private static Object Lock_HandleError = new Object();
    public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
    {
        QueuedErrors.Enqueue(new Tuple<Exception, String>(ex, DateTime.Now));
        ThreadPool.QueueUserWorkItem(()=>((App)App.Current).Dispatcher.Invoke((Action)
                () => {
                    lock (Lock_HandleError)
                        Tuple<Exception, DateTime> currentEx;
                        while (QueuedErrors.TryDequeue(out currentEx))
                            MessageBox.Show(
                               currentEx.Item1, // The exception
                               "MUS Application Error", 
                               MessageBoxButton.OK, 
                               MessageBoxImage.Error);
                }))
        );
    
    private static ConcurrentStack<Tuple<DateTime, Exception, String, bool, bool>> ErrorStack = new ConcurrentStack<Tuple<DateTime, Exception, String, bool, bool>>();
    private static bool ExceptionHandlingTerminated = false;
    private static bool ErrorBeingHandled = false; //Only one Error can be processed at a time
    
    public static void HandleError(Exception ex, bool showMsgBox) { HandleError(ex, "", showMsgBox, true); }
    public static void HandleError(Exception ex, string extraInfo, bool showMsgBox) { HandleError(ex, extraInfo, showMsgBox, true); }
    public static void HandleError(Exception ex, string extraInfo = "", bool showMsgBox = true, bool resetApplication = true)
    {
        if( ExceptionHandlingTerminated || App.Current == null) return;
        if( ErrorBeingHandled )
        {   //Queue up this error, it'll be handled later. Don't bother if we've already queued up more than 10 errors, we're just going to be terminating the application in that case anyway.
            if( ErrorStack.Count < 10 )
                ErrorStack.Push(new Tuple<DateTime, Exception, String, bool, bool>(DateTime.Now, ex, extraInfo, showMsgBox, resetApplication)); //Thread safe tracking of how many simultaneous errors are being thrown
            return;
        }
    
        ErrorBeingHandled = true;
        try
        {
            if( Thread.CurrentThread != Dispatcher.CurrentDispatcher.Thread )
            {
                ErrorBeingHandled = false;
                Invoke_HandleError( ex, extraInfo, showMsgBox, resetApplication );
                return;
            }
            if( ErrorStack.Count >= 5 )
            {
                ExceptionHandlingTerminated = true;
                Tuple<DateTime, Exception, String, bool, bool> errParams;
                String errQueue = String.Concat(DateTime.Now.ToString("hh:mm:ss.ff tt"), ": ", ex.Message, "\n");
                while( ErrorStack.Count > 0 )
                {
                    if( ErrorStack.TryPop(out errParams) )
                    {
                        errQueue += String.Concat(errParams.Item1.ToString("hh:mm:ss.ff tt"), ": ", errParams.Item2.Message, "\n");
                    }
                }
                extraInfo = "Too many simultaneous errors have been thrown in the background:";
                throw new Exception(errQueue);
            }
    
            if( !((App)App.Current).AppStartupComplete )
            {   //We can't handle errors the normal way if the app hasn't started yet.
                extraInfo = "An error occurred before the application could start." + extraInfo;
                throw ex;
            }
    
            if( resetApplication )
            {
                ((MUSUI.App)App.Current).ResetApplication();
            }
            if( showMsgBox )
            {
                //(removed)... Prepare Error message
    
                //IF the UI is processing a visual tree event (such as IsVisibleChanged), it throws an exception when showing a MessageBox as described here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/44962927-006e-4629-9aa3-100357861442
                //The solution is to dispatch and queue the MessageBox. We must use BeginInvoke because dispatcher processing is suspended in such cases.
                Dispatcher.CurrentDispatcher.BeginInvoke((Action<Exception, String>)delegate(Exception _ex, String _ErrMessage)
                {
                    MessageBox.Show(App.Current.MainWindow, _ErrMessage, "MUS Application Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    ErrorHandled(_ex); //Release the block on the HandleError method and handle any additional queued errors.
                }, DispatcherPriority.Background, new object[]{ ex, ErrMessage });
            }
            else
            {
                ErrorHandled(ex);
            }
        }
        catch( Exception terminatingError )
        {
            ExceptionHandlingTerminated = true;
            //A very serious error has occurred, such as the application not loading, and we must shut down.
            Dispatcher.CurrentDispatcher.BeginInvoke((Action<String>)delegate(String _fatalMessage)
            {
                MessageBox.Show(_fatalMessage, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Stop);
                if( App.Current != null ) App.Current.Shutdown(1);
            }, DispatcherPriority.Background, new object[] { fatalMessage + "\n" + terminatingError.Message });
        }
    }
    
    //The set of actions to be performed when error handling is done.
    private static void ErrorHandled(Exception ex)
    {
        ErrorBeingHandled = false;
    
        //If other errors have gotten queued up since this one was being handled, or remain, process the next one
        if(ErrorStack.Count > 0)
        {
            if( ExceptionHandlingTerminated || App.Current == null) return;
            Tuple<DateTime, Exception, String, bool, bool> errParams;
            //Pop an error off the queue and deal with it:
            ErrorStack.TryPop(out errParams);
            HandleError(errParams.Item2, errParams.Item3, errParams.Item4, errParams.Item5);
        }
    }
    
    //Dispatches a call to HandleError on the UI thread.
    private static void Invoke_HandleError(Exception ex, string extraInfo, bool showMsgBox, bool resetApplication)
    {
        ((App)App.Current).Dispatcher.BeginInvoke((Action<Exception, string, bool, bool>)
            delegate(Exception _ex, string _extraInfo, bool _showMsgBox, bool _resetApplication)
            {
                ErrorHandled(_ex); //Release the semaphore taken by the spawning HandleError call
                HandleError(_ex, _extraInfo, _showMsgBox, _resetApplication);
            }, DispatcherPriority.Background, new object[] { ex, extraInfo, showMsgBox, resetApplication });
    }