none
请教一个异步的最佳实践 RRS feed

  • 问题

  • Hi,各位:

    现在有一程序需要做一些运算然后将数据以绘图的方式呈现,具体需求是定时(用一Threading.Timer)从后台取数据,然后进行大量计算,然后用计算的数据更新界面,整个流程最长可达7S。默认该流程是每隔一段时间重新来一次,但是该界面需要接受用户输入,比如用户切换源,此时所有数据就需要新计算绘制。如果当前Timer的回调函数正在执行,重新来一次就可能会两个线程同时操作对象,造成不可预计的结果。同时后台还会有事件比如所有数据源清空,此时也需要重新绘制。

    接触的两个界面都是如此结构,所以觉得此结构是否应该很常见,是否有标准模式?

    现在我能想到的就是,所有Timer回调函数启动时必须先检查是否有计算或绘制在跑,这个检查就靠AutoResetEvent。希望新的流程会终止当前正在跑的,但是这个目前不知道有什么方法,当前源码的函数堆栈都比较深,不可能简单的用一个if (_isInterrupt)return就能终止,现在想到的办法就是goto或者抛异常。而且插入判断的地方可能会有很多……。及时这样仍然不知道能不能来及响应用户的点击……

    程序还有严格的流程,比如另一个图表刷新需要一个变量,而这个变量是另一个图表的依赖属性的绑定……这个可能在全用UI线程跑的流程是合理的,但是放到异步就很麻烦了。

    所以请教几个问题

    程序在设计之初就需要考虑多线程吗?怎么区分某一步用同步计算,某一步应用后台计算?锁相对于创建临时对象封送给各个线程来说那个成本更低?如果新开发一个类似结构的程序上述流程是否合理?

    希望能收到一些建议,谢谢。


    BestWishes

    2013年4月10日 8:52

全部回复

  • 为了获得更好的支持,我已把该帖从WPF论坛移到当下论坛。

    谢谢您的理解。


    Yoyo Jiang[MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2013年4月12日 5:27
    版主
  • 你那个“_isInterrupt”恐怕应该定义成volatile类型的,便于线程间共享变量,以防止变量在线程间不同步导致的问题。

    明显锁的成本高。


    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月12日 5:44
    版主
  • 我在那个判断用的是一个int 判断用的是InterLocked类型。我想考虑这么做会不会有性能提升。

    我的程序是这样的

    _timer = new Threading.Timer(MainRefresh,null, 5000,-1) _lock = new Object() MainRefresh() { lock(_lock) { _autoresetEvent.waitone() _autoresetEvent.reset() }

    //Point B do SomeThing Need 800ms Dispathcer.Invoke{ () =》 { SecondRefresh() } _autoresetEvent.set() } SecondRefresh() { // Check Ui Property if (!Ui.needRefresh()) return; task = new () =》 { lock(_lock) { _autoresetEvent.waitone() _autoresetEvent.reset() }

    //point A some thing need 100ms} // need UPdatedUI task.continuewith( () => { //UI update _autoresetEvent.set() },Uicontext);


    程序本身可能在略微复杂一点,SecondRefresh()函数在每次窗口大小变化的时候都会再次调用。现在的问题是只是这么写仍然会出现PointA 会同时有两个线程运行,或者PointA 和PointB同时运行

    想不明白。


    BestWishes

    2013年4月12日 5:58
  • 你的程序貌似不完整。

    另外你的_lock变量是静态的不?请保证是静态的。


    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月12日 6:05
    版主
  • 嗯,详细源码太长了,不过主要流程就是这样子。

    为什么要保持是静态的。我的是用的单件中的一个公开字段。我怕静态变量对回收机制不友好。



    BestWishes

    2013年4月12日 6:25
  • 如果不是静态的,我恐怕你没创建一个实例这个锁也跟着创建一遍,结果变成每个单独的实例锁着自己的锁,实质上没有起到作用。

    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月12日 6:26
    版主
  • 实际上用的是单例的公开字段。

    public Object Locker = new Object();

    但是即使这样还是有线程同时运行了?问题在哪?


    BestWishes

    2013年4月12日 7:12
  • 你就给出单例模式完整代码

    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月12日 7:33
    版主
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    
    namespace Cubic.MarketMonitorWindow
    {
        public class ThreadSynchronizationHelpler
        {
            // 主循环的Event
            public AutoResetEvent MainRefershEvent { get; set; }
    
            // 次循环的Event
            public AutoResetEvent MonitorRefreshEvent { get; set; }
    
            public object Locker = new object();
            private static ThreadSynchronizationHelpler _instance;
    
            public int IsRequestMainRefhresInterrupt = 0;
            public int IsRequestMonitorRefhresInterrupt = 0;
            public static ThreadSynchronizationHelpler Instance
            {
                get
                {
                    if (_instance != null)
                        return _instance;
                    return new ThreadSynchronizationHelpler();
                }
    
            }
    
            // 构造方法
            public ThreadSynchronizationHelpler()
            {
                MainRefershEvent = new AutoResetEvent(true);
                MonitorRefreshEvent = new AutoResetEvent(true);
                IsRequestMainRefhresInterrupt = 0;
            }
    
            public void InterruptMainRefresh()
            {
                Interlocked.Exchange(ref IsRequestMainRefhresInterrupt, 1);
            }
    
            public void InterruptMonitorRefresh()
            {
                Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 1);
            }
    
            // 再循环中调用,因为循环中的堆栈可能很长所以想通过抛异常返回。
            public void ThrowIfMainRefhresInterrupt()
            {
                if (IsRequestMainRefhresInterrupt == 1)
                {
                    throw new InterruptMainRefreshException();
                }
            }
    
    
            public void ThrowIfMonitorRefhresInterrupt()
            {
                if (IsRequestMonitorRefhresInterrupt == 1)
                {
                    throw new InterruptMonitorRefreshException();
                }
            }
    
          
            //初始化主循环是等待所有线程执行完毕。
            public void InitMainRefresh()
            {
                lock (Locker)
                {
                    InterruptMainRefresh();
                    InterruptMonitorRefresh();
                    MainRefershEvent.WaitOne();
                    MainRefershEvent.Reset();
                    MonitorRefreshEvent.WaitOne();
                    Interlocked.Exchange(ref IsRequestMainRefhresInterrupt, 0);
                    Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 0); 
                }
            }
    
            public void InitMonitorRefresh()
            {
                lock (Locker)
                {
                    InterruptMonitorRefresh();
                    MainRefershEvent.WaitOne();
                    MonitorRefreshEvent.WaitOne();
                    MonitorRefreshEvent.Reset();
                    Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 0); 
                }
            }
        }
    }
    
    麻烦你了

    BestWishes

    2013年4月12日 7:49
  • namespace Cubic.MarketMonitorWindow { public class ThreadSynchronizationHelpler { // 主循环的Event public AutoResetEvent MainRefershEvent { get; set; } // 次循环的Event public AutoResetEvent MonitorRefreshEvent { get; set; } public static object Locker = new object(); private static volatile ThreadSynchronizationHelpler _instance; public int IsRequestMainRefhresInterrupt = 0; public int IsRequestMonitorRefhresInterrupt = 0; public static ThreadSynchronizationHelpler Instance { get { if (_instance != null)lock(Locker)
    {
    if(_instance!=null)
    {
    _instance=new ThreadSynchronizationHelper();

    }
    }
    return _instance; } } // 构造方法 public ThreadSynchronizationHelpler() { MainRefershEvent = new AutoResetEvent(true); MonitorRefreshEvent = new AutoResetEvent(true); IsRequestMainRefhresInterrupt = 0; } public void InterruptMainRefresh() { Interlocked.Exchange(ref IsRequestMainRefhresInterrupt, 1); } public void InterruptMonitorRefresh() { Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 1); } // 再循环中调用,因为循环中的堆栈可能很长所以想通过抛异常返回。 public void ThrowIfMainRefhresInterrupt() { if (IsRequestMainRefhresInterrupt == 1) { throw new InterruptMainRefreshException(); } } public void ThrowIfMonitorRefhresInterrupt() { if (IsRequestMonitorRefhresInterrupt == 1) { throw new InterruptMonitorRefreshException(); } } //初始化主循环是等待所有线程执行完毕。 public void InitMainRefresh() { lock (Locker) { InterruptMainRefresh(); InterruptMonitorRefresh(); MainRefershEvent.WaitOne(); MainRefershEvent.Reset(); MonitorRefreshEvent.WaitOne(); Interlocked.Exchange(ref IsRequestMainRefhresInterrupt, 0); Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 0); } } public void InitMonitorRefresh() { lock (Locker) { InterruptMonitorRefresh(); MainRefershEvent.WaitOne(); MonitorRefreshEvent.WaitOne(); MonitorRefreshEvent.Reset(); Interlocked.Exchange(ref IsRequestMonitorRefhresInterrupt, 0); } } } }


    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月12日 8:00
    版主
  • 把Locker设置为static并且初始化_instance加锁我明白了。但是为什么把_instance标识为volatile 是因为修改了它其中的属性吗?

    本来我是用IsRequestCancle字段标识是否取消,然后抛出异常取消。这样做代价是不是很高?我第一个Refresh因为有网络连接需要700MS左右。第二个可能只需要40MS。

    假如为了少用锁而重写我其中要保存几个字典,其中字典含有6000左右个对象,每个对象大概有15个double,这样的话就是(4byte*15*6000)/1024=351K。每个线程需要保存可能1M的对象。这样的话相对于用AutoResetEvent加上抛异常取消哪个更合适?临界点在哪?


    BestWishes

    2013年4月13日 0:37
  • _instance设置为volatile的目的在于防止程序执行流程因为编译器优化导致顺序不同产生错误,同时方便线程间同步变量。

    IsRequestCancle:建议也定义成volatile为佳。


    If you think one reply solves your problem, please mark it as An Answer, if you think someone's reply helps you, please mark it as a Proposed Answer

    Help by clicking:
    Click here to donate your rice to the poor
    Click to Donate
    Click to feed Dogs & Cats

    2013年4月13日 1:06
    版主
  • 你可以尝试用Task Parrallel library
    设置一个变量比如 isComputing,启动Task时,设置isComputing = true

    当需要重新计算时,检查isComputing == true, 用token取消Task, 取消成功后,启动下一个计算,计算成功设置isComputing = false

    2013年4月19日 7:36