问题在这里http://bbs.csdn.net/topics/390369970
既要阻塞UI线程等待非UI线程结束,又要在非UI线程中进行UI操作,简单的Wait肯定会造成死锁。
因为很多时候UI与非UI操作夹杂在一起,写程序很方便。
为了解决这个问题,所以我写了一个比较通用的基于fastCSharp处理类。
就是建立一个UI任务队列,UI线程Wait新任务,非UI线程Pulse唤醒UI线程。
using System;
using System.Threading;
using fastCSharp;
using fastCSharp.threading;namespace showjim.console
{
    /// <summary>
    /// UI多任务阻塞处理
    /// </summary>
    public class uiWait
    {
        /// <summary>
        /// 任务信息
        /// </summary>
        private class taskInfo
        {
            /// <summary>
            /// 执行委托
            /// </summary>
            public action Action;
            /// <summary>
            /// 等待完成锁
            /// </summary>
            public object WaitLock;
            /// <summary>
            /// 任务是否已完成
            /// </summary>
            public bool isFinally;
            /// <summary>
            /// 等待任务完成
            /// </summary>
            public void Wait()
            {
                Monitor.Enter(WaitLock);
                try
                {
                    if (!isFinally) Monitor.Wait(WaitLock);
                }
                finally { Monitor.Exit(WaitLock); }
            }
            /// <summary>
            /// 执行任务
            /// </summary>
            public void Run()
            {
                if (Action != null)
                {
                    try
                    {
                        Action();
                    }
                    catch (Exception error)
                    {
                        fastCSharp.log.Default.Add(error, null, false);
                    }
                }
                if (WaitLock != null)
                {
                    isFinally = true;
                    Monitor.Enter(WaitLock);
                    try
                    {
                        Monitor.Pulse(WaitLock);
                    }
                    finally { Monitor.Exit(WaitLock); }
                }
            }
        }
        /// <summary>
        /// 非UI任务
        /// </summary>
        private readonly task task;
        /// <summary>
        /// UI任务集合
        /// </summary>
        private list<taskInfo> tasks = new list<taskInfo>();
        /// <summary>
        /// UI任务访问锁
        /// </summary>
        private readonly object taskLock = new object();
        /// <summary>
        /// 是否开始运行UI任务
        /// </summary>
        private bool isRun;
        /// <summary>
        /// 是否以线程的方式运行UI任务
        /// </summary>
        private bool isRunThread;
        /// <summary>
        /// 非UI任务是否已完成
        /// </summary>
        private bool isTaskFinally;
        /// <summary>
        /// UI任务是否已完成
        /// </summary>
        private bool isFinally;
        /// <summary>
        /// UI任务完成锁
        /// </summary>
        private readonly object finallyLock = new object();
        /// <summary>
        /// UI多任务阻塞处理
        /// </summary>
        /// <param name="task">非UI任务</param>
        public uiWait(task task)
        {
            if (task == null) fastCSharp.log.Default.Throw(log.exceptionType.Null);
            this.task = task;
        }
        /// <summary>
        /// 添加新的UI任务
        /// </summary>
        /// <param name="task">任务信息</param>
        private void add(taskInfo task)
        {
            Monitor.Enter(taskLock);
            try
            {
                tasks.Add(task);
                Monitor.Pulse(taskLock);
            }
            finally { Monitor.Exit(taskLock); }
        }
        /// <summary>
        /// 添加新的UI任务
        /// </summary>
        /// <param name="run">任务执行委托</param>
        public void Add(action run)
        {
            if (run != null) add(new taskInfo { Action = run });
        }
        /// <summary>
        /// 添加新的UI任务
        /// </summary>
        /// <typeparam name="parameterType">任务参数类型</typeparam>
        /// <param name="run">任务执行委托</param>
        /// <param name="parameter">任务参数</param>
        public void Add<parameterType>(action<parameterType> run, parameterType parameter)
        {
            if (run != null) add(new taskInfo { Action = run<parameterType>.Create(run, parameter) });
        }
        /// <summary>
        /// 添加新的UI任务并同步等待任务完成
        /// </summary>
        /// <param name="info">任务信息</param>
        private void addWait(taskInfo info)
        {
            add(info);
            info.Wait();
        }
        /// <summary>
        /// 添加新的UI任务并同步等待任务完成
        /// </summary>
        /// <param name="run">任务执行委托</param>
        public void AddWait(action run)
        {
            addWait(new taskInfo { Action = run, WaitLock = new object() });
        }
        /// <summary>
        /// 添加新的UI任务并同步等待任务完成
        /// </summary>
        /// <typeparam name="parameterType">任务参数类型</typeparam>
        /// <param name="run">任务执行委托</param>
        /// <param name="parameter">任务参数</param>
        public void AddWait<parameterType>(action<parameterType> run, parameterType parameter)
        {
            addWait(new taskInfo { Action = run<parameterType>.Create(run, parameter), WaitLock = new object() });
        }
        /// <summary>
        /// 检测是否开始运行UI任务
        /// </summary>
        private void checkRun()
        {
            Monitor.Enter(taskLock);
            try
            {
                if (isRun) fastCSharp.log.Default.ThrowReal(log.exceptionType.ErrorOperation);
                isRun = true;
            }
            finally { Monitor.Exit(taskLock); }
        }
        /// <summary>
        /// 等待非UI任务结束
        /// </summary>
        private void wait()
        {
            task.Dispose(true);
            isTaskFinally = true;
            Monitor.Enter(taskLock);
            try
            {
                Monitor.Pulse(taskLock);
            }
            finally { Monitor.Exit(taskLock); }
        }
        /// <summary>
        /// 执行UI任务
        /// </summary>
        private void run()
        {
            list<taskInfo> runTasks = new list<taskInfo>(), oldTasks;
            while (true)
            {
                Monitor.Enter(taskLock);
                try
                {
                    if (tasks.Count == 0)
                    {
                        if (isTaskFinally) break;
                        Monitor.Wait(taskLock);
                    }
                    oldTasks = runTasks;
                    runTasks = tasks;
                    tasks = oldTasks;
                }
                finally { Monitor.Exit(taskLock); }
                foreach (taskInfo task in runTasks) task.Run();
                runTasks.Empty();
            }
            isFinally = true;
            Monitor.Enter(finallyLock);
            try
            {
                Monitor.Pulse(finallyLock);
            }
            finally { Monitor.Exit(finallyLock); }
        }
        /// <summary>
        /// 异步执行UI任务
        /// </summary>
        public void RunThread()
        {
            checkRun();
            new thread(wait).Start();
            new thread(run).Start();
            isRunThread = true;
        }
        /// <summary>
        /// 等待异步执行UI任务结束
        /// </summary>
        public void WaitThread()
        {
            if (isRunThread)
            {
                Monitor.Enter(finallyLock);
                try
                {
                    if (!isFinally) Monitor.Wait(finallyLock);
                }
                finally { Monitor.Exit(finallyLock); }
            }
            else fastCSharp.log.Default.ThrowReal(log.exceptionType.ErrorOperation);
        }
        /// <summary>
        /// 执行UI任务并等待任务结束
        /// </summary>
        public void Wait()
        {
            checkRun();
            new thread(wait).Start();
            run();
        }
    }
}
支持同步与异步两种模式,uiWait是一个实例
            uiWait.Wait();
            uiWait.RunThread();
            //do something
            uiWait.WaitThread();UI线程阻塞多任务

解决方案 »

  1.   


    if (run != null) add(new taskInfo { Action = run<parameterType>.Create(run, parameter) });报错
      

  2.   

    if (run != null) add(new taskInfo { Action = run<parameterType>.Create(run, parameter) });报错!
      

  3.   

    编译错误?我的vs2010没有问题,你的是vs2012吗?
      

  4.   

    谁跟你说EventHandler的WaitOne“肯定造成死锁”?之所以不让UI线程阻塞,是要让程序注重起码的用户体验。而你杜撰出来“肯定造成死锁”,先恐吓一下,就会让那些不懂技术的人盲目跟风,更不懂如何阻塞了。你看似抛出了一个榴莲,其实目的是不让人家吃水果了,只能吃你的榴莲。
      

  5.   

    假设主线程调用了 EventWaitHandler.WaitOne 阻塞了,那么子线程只要把要在主线程中只要调用其对应的Set,不让其画蛇添足地去阻塞就行了。而子线程在Set后需要调用控件的 Invoke 才能让主线程显示子线程的运行结果,这个是标准的概念。这一切都源自一个完全胡乱的假设“既要阻塞UI线程等待非UI线程结束”。这叫什么啊?既然你要强行“等待”还搞什么“子线程”?你运用名词概念时有原则吗?
      

  6.   

    不为lz,为了其它人,我来说明下起码的概念原则。比如说(恰好是在)主线程运行到一定阶段,你想执行一个子任务,那么就应该启动一个子线程。而主线程为了起码的用户体验,该干什么就还是干什么去,它是与子线程并行的,根本谈不上阻塞。然后子线程中运算完,在Invoke中调用方法,这个方法里的代码就会在主线程中继续执行了。也就是说,所有需要等待子线程的计算完毕后才在“主线程执行”的代码,是作为委托注册给子线程作为Invoke委托其执行的。如果你阻塞了主线程,同时看着子线程在那里运行,这叫做“并行执行”吗?这叫做画蛇添足。这还不如一个主线程自己执行呢。
      

  7.   

    该代码不支持WPF,直接报错,而且还用了不成熟的fastCSharp,看介绍不错,但实际里面的很多方法在.NET4.0里面早就有了,只不过被他提取了出来,自然稳定性没有内置的好。
      

  8.   


    VS2012 没有引用fastCSHarp ,我去找个试下
      

  9.   

    谁跟你说EventHandler的WaitOne“肯定造成死锁”?
    之所以不让UI线程阻塞,是要让程序注重起码的用户体验。而你杜撰出来“肯定造成死锁”,先恐吓一下,就会让那些不懂技术的人盲目跟风,更不懂如何阻塞了。你看似抛出了一个榴莲,其实目的是不让人家吃水果了,只能吃你的榴莲。
    既然这样,那就麻烦大神你也抛一个水果出来,让大家学习学习啊,谢谢。
    这个需求可不是我自己假设出来的,应该是http://bbs.csdn.net/topics/390369970楼主的真实需求吧。
    当然也可能是我对楼主的需求理解错误,那么也有请大神抛一个水果出来,解决一下实际问题。
    如果是一个子线程,确实不如直接嵌入主线程执行,这个建议我已经在楼主帖子的回帖中说过了。
    我以为楼主的需求是多个子线程需要并行,至于阻塞主线程也是楼主的需求。我在楼主的回帖中说过,这种程序流程的设计是不正常的,我只是想尽可能少的改动源代码,以解决这个问题。
      

  10.   

    不好意思,我没有做过WPF,不知道报的什么错误?能不能麻烦截个图看看,谢谢!
    fastCSharp是支持.NET2.0的,开发效率与运行效率都优于.NET原生代码。
    至于稳定性,我是应用于自己所做的实际项目的,应该也不会太差。
      

  11.   

    不知道你有没有看readme.txt
    要编译.net3.5及以上版本,需要在 项目属性->生成 设置条件编译符号 DOTNET35。
      

  12.   

    不论winform还是wpf,甚至web,UI都是单线程的,所以一边只要保证一个线程循环处理界面事件即可,还需要队列,如果有需要,可以设计几个优先级别不一样的队列,然后设计一个类,用来存放事件和参数,当有事件的时候,存入该队列,并增加一个信号量,循环线程则取队列,然后处理;由于采用了多线程,界面处理的入口一半放置到主窗体下,并在主窗体使用异步方式调用,BeginInvoke和Invoke来执行,再根据需要,分发到各组件和各窗体进行处理;具体思路都是这样,一个事件类,一个事件队列,一个线程循环,配合线程锁和信号亮,来实现多任务UI处理,封装好后,比较理想的状态是:一个事件添加入口,一个事件处理出口,一个事件抛弃接口,一个线程终止接口(退出程序事用);
      

  13.   

    如果我没猜错,你这个方法必须对UI操作的部分进行封装,然后传递给uiWait进行并行处理。显然这个封装是最麻烦的,如果一个耗时的操作中频繁穿插UI操作,那么封装的数量将会非常多,如果整体封装,那么调用你的方法没任何效果,因为耗时操作转个弯又跑到了UI线程中去执行了,最终的表现仍旧是卡界面。我这里要强调的是,卡界面不是因为Wait信号造成的,更多的是代码本身和UI打交道的时间过长造成的,比如循环列表每行数据进行改动。
      

  14.   

    DOTNET35符号添加了,是WPF回调方式和WinForm有显著不同,它是通过一个叫Dispatcher的管理器来统一管理线程的,你不能通过控件进行Invoke。
    其实我只是要试验下你的代码能实现的效果,我也看了下微软的async/await关键字的处理,最终确定要实现完美的界面无阻塞,不是那么简单的,微软背后进行了偷梁换柱才实现了这样的效果,而你的代码就那么点内容,绝对不可能实现那种效果。
      

  15.   

    确实子线程中的是每一个UI操作都要封装成任务,但是这个改动代价应该是最小的,因为它只需要
    uiWait.Add(()=>{...});
    包装起来是可以了,不需要Invoke,因为最后是在主线程中执行的任务。如果是封装数量多导致UI卡住(不是死锁),那么Invoke只会更慢。
    能不能实现,测试过了就知道了。
      

  16.   

    需要同步的话uiWait.AddWait(()=>{...});
      

  17.   

    楼主的需求就是在子线程执行的时候阻塞UI线程。
    如果没有阻塞UI线程的需求,根本就没必要用到这个uiWait。
      

  18.   

    有一种需求,你看能否实现,微软在.NET4.5里面实现了。
    首先是一段正常执行的代码,因为耗时非常少,所以放在UI线程中直接执行,然后一段非常耗时的代码进来了,要不卡界面,只能用多线程执行,但是我需要在这个耗时代码结束的时候,继续执行UI操作,虽然可以通过委托回调的方式,在多线程结束的时候回调去处理,但是这样代码看起来就被分隔开了,不易读懂,编码量也增加了。微软在NET4.5里面是这样处理的,非常耗时的多线程操作作为UI线程的不部分,通过await关键字去等待该部分的执行结束,而这个等待可以做到不卡界面,只要执行的过程是异步的(同步的过程仍旧会卡界面的),如果不用await去等待,异步执行后面的代码会立刻执行,用了await去等待,后面的代码会等待这个异步过程结束后才去执行,并且等待中不卡界面。
    使用async/await关键字后,整个多线程编程过程中,随意插进来的多线程部分等待和同步等待一样进行编码,代码看起来非常舒服。(编译后的IL偷梁换柱,很难读懂的)
      

  19.   

    fastCSharp.threading.asynchronous就是用来干这事的,不过现在不通用,只支持Stream的异步操作,因为这两个我用到过所以就写了这两。
    要写个通用的也容易,那就没法使用IO线程干活了,只能自己开线程或者添加到某个任务中执行。现在我还没碰到过这种需求,所以就没有写这部分,具体实现可以参考Stream。
      

  20.   

    比如我的.NET2.0项目代码                using (StreamReader srOut = process.StandardOutput, srError = process.StandardError)
                    {
                        asynchronous.streamEndReader outputReader = srOut.BaseStream.readToEndAsync();
                        asynchronous.streamEndReader errorReader = srError.BaseStream.readToEndAsync();
                        process.WaitForExit(10000);                    if (!process.HasExited)
                        {
                            KillProcess(process);
                            item.State = dataModel.judge.judgeState.CompileError;
                            item.Message = "编译超时";
                            //item.Message += outputReader.Wait().ToString(Encoding.Default);
                            //item.Message += errorReader.Wait().ToString(Encoding.Default);
                        }
                        else
                        {
                            item.Message += outputReader.Wait().ToString(Encoding.Default);
                            item.Message += errorReader.Wait().ToString(Encoding.Default);                        if (process.ExitCode != 0)
                                item.State = dataModel.judge.judgeState.CompileError;
                            else
                                item.State = dataModel.judge.judgeState.Compiled;
                        }
    里面的用法                    asynchronous.streamEndReader outputReader = srOut.BaseStream.readToEndAsync();
                        asynchronous.streamEndReader errorReader = srError.BaseStream.readToEndAsync();
    //...
                            item.Message += outputReader.Wait().ToString(Encoding.Default);
                            item.Message += errorReader.Wait().ToString(Encoding.Default);
      

  21.   

    这难道是传说中的动态编译?我看到Compile关键字了。
    你这样给代码比较难理解,最好有个使用部分的完整Demo,显然你只是在说明你的核心代码如何在工作,但我现在对你的代码如果调用还不清楚,自然要演示一个效果出来也难。我需要先看到它能做到什么,通过实际的代码来说明,然后才去看它如何实现的。
      

  22.   

    我贴的是应用代码,与核心没有关系,核心代码在asynchronous.cs里面。这个Compile是OJ应用中的,与async/await没有任何关系。asynchronous.streamEndReader outputReader = srOut.BaseStream.readToEndAsync();
    就是产生一个异步任务outputReader.Wait()
    就是等待任务结束,相当于await
      

  23.   

    能不能给一个最简单的应用。
    一个窗口,里面一个按钮,按钮添加一个点击事件。
    事件的开头先new另一个窗口出来,那个窗口里面只有一个进度条,开启自己滚动的效果。
    在show这个窗口出来后,执行另一个耗时的方法,这个方法就一行代码Thread.Sleep(10000);
    这个耗时方法结束后,关闭打开的窗口。我需要的效果是,在这个10秒内,界面不会卡,不会卡的表现就是那个新建的窗口中的滚动条能够正常滚动。同时这个窗口必须等待10秒后关闭,而所有这些方法要写在按钮的点击事件中,只有那个耗时的方法(10秒等待)是封装的,为了开启多线程。
      

  24.   

    你说的这个fastCSharp.threading.asynchronous不能实现,因为Wait()会阻塞当前线程。uiWait也许能实现,关键是滚动效果必须是自己的代码实现的,如果是.NET自己封装的就不行。
      

  25.   

    这个需求可以修改一个uiWait.AddWait使它支持返回值就可以了。说明,uiWait的UI操作必须是自己写的代码,而不是.NET内部的无法使用.Add包装的代码。
      

  26.   

    看来离我预期的差太远了,在.NET中,开启多线程其实很容易的,而且方法也有很多种,仅仅要实现多线程的话,完全没必要封装调用这个uiWait,我自己创建委托,自己调用还好控制些,只有能达到我上述要求的才有封装的意义。
      

  27.   

    如果uiWait.AddWait改为支持返回值的话,伪代码基本是这样的            fastCSharp.threading.task task = new fastCSharp.threading.task(2);
                uiWait = new uiWait(task);
                进度条 进度条 = new 进度条();
                task.Add(() =>
                {
                    Thread.Sleep(10000);
                    uiWait.Add(() => 进度条.关闭());
                });
                task.Add(() =>
                {
                    while (uiWait.AddWait(() =>
                    {
                        if (!进度条.是否关闭())
                        {
                            进度条.进度 = 获取当前进度();
                            return true;
                        }
                        return false;
                    }))
                    {
                        Thread.Sleep(1);
                    }
                });
                uiWait.Wait();
    不好意思,只能给个伪代码,我对winform不熟悉,只会拖个按钮什么的。
      

  28.   

    看来你没理解我的意思,我所谓能看到进度条滚动,只是不卡界面的表现,事实上整个界面是完全无阻塞的,可以随时响应任何用户消息,就好像上面没有执行任何代码一样,但实际上后台在执行,而且是在异步执行,至于在异步执行后面的同步等待,在.NET4.5里面被偷偷地注册到了异步后的回调方法中去了。所以整个按钮事件必须用async修饰,标识它是支持异步的,虽然看似是同步执行的一个事件,实际上微软偷偷将其作为异步执行在处理,自动处理里面的同步部分。
    但是这个功能想要模拟,难度非常大,而.NET4.5不支持XP,因此又不敢去用。
      

  29.   

    我对winform基本不会用,但是UI线程的概念还是有的。
    你能不能提供这个简单的源代码,我试试使用uiWait改写。
      

  30.   

    我知道你说的什么意思,这个我也曾今想过,没什么简单的好办法。
    可以考虑自己解析LambdaExpression,对其中的UI操作改为Invoke,这个工作量不小。
      

  31.   

    这样做不如直接在子线程中处理,自己写一个通用的Invoke,所有UI操作都调用这个Invoke,这样比较省事。
      

  32.   

    这个uiWait主要目的就是要阻塞主线程的,与你的需求完全是背离的。
      

  33.   

    对于第一种设想,同步中插入异步的,我打算让进度条窗口在另一个线程中运行,而UI线程就让它卡死,只是不给用户去点击,只能点击新开的窗口,这样用户体验就不会太糟糕。对于第二种设想,我通过SynchronizationContext(同步上下文)实现了,在异步执行的方法中,穿插进来的所有同步操作,我发送到SynchronizationContext上面去操作,组织下代码格式后,看起来也很清晰。
      

  34.   

    谢谢你提供的解决方案。由于我对winform不是太熟,有一个疑问:
    进度条窗口可以在UI线程卡死的情况下运行吗?还是说,那是一个新的窗口进程?以前不知道SynchronizationContext这个东西,看了一下介绍,像是一个.NET平台通用的“Invoke”。谢谢!
      

  35.   

    我不是写得很清楚了吗:“我打算让进度条窗口在另一个线程中运行”
    那个是新建的STA线程,和后台线程有点不同,能够运行UI控件,你理解为一个程序同时开了2个不相干的窗口即可,进程显示的还是一个,但2个窗口互相之间不会干扰。虽然如此,但我可以通过设置静态变量来访问共享对象,进而干预另一个窗口。而这里也可以使用SynchronizationContext将这个线程的任务发送到另一个线程去执行,从而用户能够随时看到处理进度,但是界面却一直能响应。
      

  36.   

    另外你说对winform不熟悉,其实论难度,WinForm < WebForm < WPF,既然你会WebForm,顺带学WinForm也完全没问题的。WinForm也只需要拖拖控件,不需要手写代码布局,代码布局得到WPF里面去做,所以WinForm简单,而WebForm有部分布局是要通过代码产生的,比如动态创建的html代码,还要考虑浏览器兼容性和运行速度,因此在界面设计上难度较大。而后台的事件响应3者基本相同,只是触发事件的模式不一样而已。
      

  37.   

    谢谢指教,我以为一个进程只有一个UI线程,所以虽然我是做web开发的,但是仅仅是使用C#而已(与你想象中的webForm没什么关系)。
    而且我对于winform也没什么兴趣,如果将来有时间倒是可以学一学wpf,但是我不想把时间花在界面布局上面。所以我的fastCSharp涉及的都是与界面无关的东西。
      

  38.   

    看了一下SynchronizationContext,要实现通用的“同步中插入异步”,基本不可能。
    除非函数功能具有严格层次关系,如果post次数过多的话代码写出来也很难看,比如            //do something
                context.post(() =>
                {
                    //do something
                    context.post(() =>
                    {
                        //do something
                        context.post(() =>
                        {
                            //do something
                        }, null);
                    }, null);
                }, null);
    这个小写的post是经过包装的。
      

  39.   

    SynchronizationContext使用起来方便,因为它的Send和Post是固定的参数,同时SynchronizationContext有通用性,不会因为WinForm或WPF而有差异。
    你的这个代码其实写的不好,SynchronizationContext不应该迭代。
    规范的代码编写方式是这样的:
    先不管是否需要异步操作,按照同步操作编写代码。全部写好后,对其中的UI操作部分进行单独处理,添加
    context.Post(t =>
    {},this);
    如果是获取变量内容,则必须用Send请求。这里的模型甚至可以用代码生成器自动产生,前提是要添加#regin标签进行代码分类,便于识别。它的好处是可读写强。如果这里不传递this对象,为空也可以,但是执行效率会降低些,因为如果不是通过变量传递,直接访问外部变量的话,这个匿名委托的处理方式就会有不同,编译器会创建一个匿名类来传递外部变量的。
      

  40.   

    sjdlkjasllfsavnlsakvskjdfasoiehdsnahasdhfalsdfhaslf