C#多线程和事件

原文(C# Multithreading and Events):http://www.codeproject.com/Articles/886223/Csharp-Multithreading-and-Events

First let’s start off with there is no perfectly safe multithreaded event handling as there are caveats/conditions that must be handled. So it’s really if the particular method you plan to use is safe for the uses you require.

There is a great article here about this:

http://www.codeproject.com/Articles/37474/Threadsafe-Events

The one suggested solution the author mentions is don’t use events in a multithreaded way. (This can be a good solution when possible.)

I’m going to show there is another problem to contend with and I will offer some classes to help handle some scenarios plus propose some alternative options you could use, but it depends on if your events can be used that way. Ultimately it depends on what you need to do, and implementation/use and the complexity you wish to manage.

The Problems

To start let’s define the types of multithreading problems that come up with trying to solve this problem:

  1. Possible Deadlocks adding and removing event due to lock(this) or lock(type) for static events
  2. Race condition calling a null event
  3. An added (subscribed) event handler is not immediately called
  4. A removed (unsubscribed) event handler is still called.
  5. Deadlocks trying to subscribe/unsubscribe
  6. Might not get the most recent list of delegates to fire
  7. Problem/Feature - Events could be fired to delegates out of sequence

The general consensus is there is no way to solve all of these issues. Perhaps but maybe let’s see the ways we can work with these issues.

Problem details

Problem #1 Possible Deadlocks adding and removing events

This should not be a problem in recent versions of .net 4.5 is reportedly using lockfree constructs that should avoid this type of issue. If you need to handle this in earlier versions you could implement your own add/remove handlers with your own lock object to avoid some potential for deadlock scenario’s.

Problem #2 Race condition calling a null event

The problem is an even by default is null until you add a handler to it. If one thread is trying to add as another is trying to raise the event, or the last handler is trying to unsubscribe, when you try to call the event you could get a null reference exception.

Problem #3 An added event handler is not immediately called

In this scenario one thread adds a handler, but an event gets raised in the middle and this handler doesn’t appear to be immediately called.

So either controlling your event source is needed, not needing to worry much about a missed event, or you would need something more like a message bus that replayed events that already happened before subscribing.

Problem #4 An event handler is removed but it is still called

In this scenario one thread removes a handler but an event is raised on another thread while this is happening and it still gets called even after it was thought to have unsubscribed.

Problem #5 Deadlock due to an event calling a handler that interacts with another thread that subscribes/unsubscribes

In this scenario it causes a deadlock because if the event mechanism holds a lock while subscribing or unsubscribing and while calling events it might be possible to deadlock. (See this article for more detailshttp://www.codeproject.com/Articles/37474/Threadsafe-Events)

Problem #6 Might not get the most recent list of delegates to fire

We will see in the recommended solution that first the code tries to take a copy of the delegate event list to fire. When it does this it’s possible it might not get the most recent list because that another thread already updated the list. This might not be that big of an issue it is similar to problem #3 in that the net result is an added event subscription might not receive a notification immediately after subscribing

Problem/Feature #7 Delegates might receive events out of sequence due

NOTE: This is more of an issue with the publisher of events controlling event ordering properly.

This can happen if multiple threads fire events at the same time, its possible they can appear to the subscriber out of the expected sequence if there was an expected sequence. Once you have decided that multiple threads can fire events this could happen at anytime even with proper locking, one thread beats another to firing an event so it appears first. A slight variation #7(B) of this is where the list of delegates starts to get one event sent to the list and then gets another event sent to it even before the first event has completed firing to the full list. If you really wanted multithreaded events you might consider this a feature to truly support multithreading. Many events do have a typical order. Such as Got focus, Lost focus, Session start, Session end (either indicated with EventArgs in your event or via separate events for each of those). The safest way to avoid this is to always fire your events from one thread if this matters and/or control the controller that decides on firing events with multithreaded synchronization to avoid it sourcing multiple events at the same time. So this problem will exist in any multithreaded scenario where that is not done. Since those are actions need to be controlled by the publisher likely with some type of state management that is not covered here.

(At the end of the article an example is shown of how to address event ordering between two events where the sequence matters)

We can exclude problem #1 since as mentioned it is not really an issue anymore. Problem #3 as mentioned almost all implementations will have unless you control your source of events/timing or implement your events like a message bus to send new subscribes all of the previous events. .NET events typically aren’t used that way.

Example of the basic problem multithreading issue for events

public ThisEventHandler ThisEvent;

protected virtual OnThisEvent (ThisEventArgs args)
{  
      If (ThisEvent!=null)
         ThisEvent (this,args);  // ThisEvent could be null so you could get a null reference exception 
}
Problem  
#2 Null exception race condition X
#3 A new subscriber might not get called immediately X
#4 A handler is still called after unsubscribe. X
#5 Possible deadlock  
#6 Might not get the most recent list of delegates to fire  
#7 Problem/Feature - Events could be fired to delegates out of sequence X

Normally Recommended solution

Let’s start with the recommended solution to understand what it handles and what it doesn’t.

public ThisEventHandler ThisEvent;

protected virtual void OnThisEvent (ThisEventArgs args)
{
   ThisEventHandler thisEvent=ThisEvent;  //assign the event to a local variable
   If (thisEvent!=null)
   {
      thisEvent (this,args);
   }
}

Solution Summary

This solves problem #2 and does not have problem #5

This has problems: #3 and #4. Which is that if you add a new handler it might not get called immediately and if you remove a handler it may still get called for a short time. Better at least, we got rid of one problem. We also see this solution introduces a possible #6 issue, it takes a copy of the list of handlers in the local thisEvent variable but because this variable is not volatile it may not get the most recent copy of the handler list.

Problem  
#2 Null exception race condition  
#3 A new subscriber might not get called immediately X
#4 A handler is still called after unsubscribe. X
#5 Possible deadlock  
#6 might not get most recent copy of handlers to call X
#7 Problem/Feature - Events could be fired to delegates out of sequence X

One way you can workaround problem #3 (adding a new handler – it doesn’t get called immediately) (if possible) is to add your handlers early before it is likely that multiple threads will be calling your event. This avoids issues with #3.

For #4 when you are unsubscribing set a bool flag that your event handler can check to see if it is being unsubscribed. If it is do nothing. Or just have smart handling in your event handler to know when this has happened. The problem is there is no safe way to do this without locking.

Let’s see if we can do better:

Example Fix for normally recommended solution (publisher)

In the event publisher if we use this code:

volatile ThisEventHandler _localEvent = null;  //need volatile to ensure we get the latest

        public virtual void OnThisEvent (EventArgs args)
        {
           _localEvent=ThisEvent;  //assign the event to a local variable – notice is volatile class field
           if (_localEvent!=null)
           {
              _localEvent(this,args);
           }
        }

When not using locks in the event publisher, notice the variable _localEvent many examples you might see on the internet save a copy of the event into a variable and then call it if not null. They typically don’t use a class volatile field. They should otherwise they might not be getting the latest copy. The net effect may not be that much of an issue using a class field vs a local variable as it would generally be expected when a method is called that the local variable will be initialized at least once. In C# local variables can’t be volatile though, so to ensure reads/writes are directed to memory a volatile class field can be used.

Example Improvement for the subscriber #1

In the event subscriber class if we use this:

private object _lock = new object();  
private volatile bool _unsubscribed=false;

private void MethodToUnsubscribe()
{
 	lock(_lock)
       {
       _unsubscribed = true;
                
       	ThisEvent -= new ThisEventHandler(ThisEventChanged);
	}
}
public void Subscribe()
{
       lock(_lock)
	{
            _unsubscribed = false;
                
            ThisEvent += new ThisEventHandler(ThisEventChanged);
       }            
}

//Event handler
private void ThisEventChanged(object sender, ThisEventArgs e) 
{
   
	lock(_lock)
{
       	if (!_unsubscribed)
       	{
			//Do work here
       	}
	}
	
}

Example Improvement for the subscriber #2 (non sequential- true multithreaded)

In the event subscriber class if we use this:

private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();  
private volatile bool _unsubscribed=false;

private void MethodToUnsubscribe()
{
 	_rwLock.EnterWriteLock();
       _unsubscribed = true;
                
       ThisEvent -= new ThisEventHandler(ThisEventChanged);
       _rwLock.ExitWriteLock();
}
public void Subscribe()
{
            
            _rwLock.EnterWriteLock();
            _unsubscribed = false;
                
            ThisEvent += new ThisEventHandler(ThisEventChanged);
            _rwLock.ExitWriteLock();
            
}

//Event handler – due to read lock could get called out of sequence by multiple threads
private void ThisEventChanged(object sender, ThisEventArgs e) 
{
   
_rwLock.EnterReadLock();
	try
	{
       	if (!_unsubscribed)
       	{
			//Do work here
       	}
	}
	finally
{
       	_rwLock.ExitReadLock();         
      }
}

Solution Summary

This unfortunately puts some burden on the subscriber of the event.

IMPORTANT: This example code also allows for multiple threads to fire calls at the same time, which might not be desired. If not desired just use a regular lock instead of the reader/writer lock otherwise your events could come out of sequence.

If you implement the publisher and subscriber code:

Problem  
#2 Null exception race condition  
#3 A new subscriber might not get called immediately  
#4 A handler is still called after unsubscribe.  
#5 Possible deadlock X
#6 might not get most recent copy of handlers to call  
#7 Problem/Feature - Events could be fired to delegates out of sequence X

If your events can be handled this way then some of the above issues are either solved or worked around. If your code can not handle the limitations of this method there are other implementations but they will suffer from one or more of the problems listed. So it will come down to your code requirements and implementation complexity about how you wish to handle multithreaded events. (Or potentially as the first article mentions, don’t do that and only use events from a single thread).

If you implement just the event publisher code and not the subscriber code:

Problem  
#2 Null exception race condition  
#3 A new subscriber might not get called immediately X
#4 A handler is still called after unsubscribe. X
#5 Possible deadlock X
#6 might not get most recent copy of handlers to call  
#7 Problem/Feature - Events could be fired to delegates out of sequence X

Both solutions solved the problem #6 shown in the standard recommended solution by using a volatile field in the publisher code to ensure the most recent copy of the event handlers is copied.

The above method shows how to handle this problem in the code that uses an event rather than in the code that publishes the event.

Let’s look at an example that wraps up the logic to handle events a bit more safely:

SafeEvent class

Let’s look at wrapper class implementation that puts this all together so that the implementer of an event can handle most of the issues to not burden the listeners/susbcribers. The below code is an implementation that can be used for event publishers. This implementation supports multithreaded non-sequential events being fired.

    /// <summary>
    /// SafeHandlerInfo - used for storing handler info, plus contains a flag 
    /// indicating if subscribed or not and the reader writer lock 
    /// for when the subscription is read or updated.
    /// </summary>
    /// <typeparam name="TArgs">Event args</typeparam>
    internal class SafeHandlerInfo< TArgs> where TArgs : EventArgs 
    {
        public EventHandler<TArgs> Handler;
        public bool Subscribed = true;
        //public object LockObj = new object();

        public ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

        public SafeHandlerInfo(EventHandler<TArgs> handler)
        {
            Handler = handler;
        }

    }

    /// <summary>
    /// <summary>
    /// SafeEvent class provides safety for unsubscribing from events so even with 
    /// multiple threads after unsubscribing it will not get called.
    /// Also makes sure that a null exception won't happen due to the removing of 
    /// events.  Only one event is fired at a time, so single threaded through this event.
    /// </summary>
    /// <typeparam name="TArgs">The type of the event args</typeparam>
    public class SafeEvent<TArgs>
        where TArgs : EventArgs
    {

        private object _lock = new object();

        private event EventHandler<TArgs> _event;

        

        public event EventHandler<TArgs> Event
        {
            add
            {
                Subscribe(value);
            }

            remove
            {
                Unsubscribe(value);
            }

        }



        /// <summary>
        /// Used to fire this event from within the class using the SafeEvent
        /// 
        /// </summary>
        /// <param name="args">The event args</param>
        public virtual void FireEvent(object sender,TArgs args)
        {

            lock (_lock)
            {
                EventHandler<TArgs> localEvents = _event;
                if (localEvents != null)
                    localEvents(sender, args);

            }

        }


        /// <summary>
        /// Unsubscribe - internally used to unsubscribe a handler from the event
        /// </summary>
        /// <param name="unsubscribeHandler">The handler being unsubscribed</param>
        protected void Unsubscribe(EventHandler<TArgs> unsubscribeHandler)
        {

            lock (_lock)
            {
                _event -= unsubscribeHandler;
            }

        }

        /// <summary>
        /// Subscribe - Called to subscribe the handler
        /// </summary>
        /// <param name="eventHandler">The handler to subscribe</param>
        protected void Subscribe(EventHandler<TArgs> eventHandler)
        {
            lock (_lock)
            {
                _event += eventHandler;
            }
        }
    }

Solution Summary

The above code solves similar problems from the publishing side.

Problems  
#2 Null exception race condition  
#3 A new subscriber might not get called immediately  
#4 A handler is still called after unsubscribe.  
#5 Possible deadlock X
#6 might not get most recent copy of handlers to call  
#7 Problem/Feature - Events could be fired to delegates out of sequence X

This code is susceptible to deadlocks under certain scenario’s

Potential Deadlock - If they unsubscribe an existing handler of the event currently being fired there could be the potential for deadlock. So the best rule to follow is don’t try to unsubscribe while in the middle of the event being called and avoid that in any downstream functions that are called by that event.

This hopefully should be very controllable because as the subscriber of the event you should know when you want to unsubscribe. This would only happen if a separate thread then the one being fired cause the unsubscribe of the current event being fired. So fairly limited chance.

Next let’s have an example of how to use the SafeEvent class:

    /// <summary>
    /// TestEventUser
    /// Test the SafeEvent class for implementing an event using SafeEvent
    /// </summary>
    public class TestEventUse
    {
        //declare our private event 
        private SafeEvent<EventArgs> _myEvent = new SafeEvent<EventArgs>();

        /// <summary>
        /// MyEvent - proxy the add/remove calls to the private event
        /// </summary>
        public event EventHandler<EventArgs> MyEvent  
        {
            add
            {
                _myEvent.Event += value;
            }

            remove
            {
                _myEvent.Event -= value;

            }

        }

        /// <summary>
        /// OnMyEvent - standard example idiom of how to fire an event
        /// </summary>
        /// <param name="args"></param>
        protected void OnMyEvent(EventArgs args)
        {

            _myEvent.FireEvent(this,args); //call our private event to fire it
        }

        /// <summary>
        /// FireEvent - This we provided on our test class as a quick way to fire the event from another class.
        /// </summary>
        public void FireEvent()
        {
            OnMyEvent(new EventArgs());
        }

    }

Truly Multithreaded Event handling (SafeEventNS class)

Most implementations of event handling you’ll see around either have many of the multithreading issues mentioned or will allow only a single thread in to fire a single event from a single thread at a time.

SafeEventsNS will allow multiple threads to fire the event at the same time and the subscriber can get multithreaded calls at the same time. This means that notifications might not come in sequence. This is not the normal way most events are handled, most event handlers probably care about the sequence of events that come in. But if you don’t and you need a relatively safe way to fire an event from multiple threads to multiple subscribers at the same time, this class might be useful.

SafeEventNS class

    /// <summary>
    /// SafeHandlerInfo - used for storing handler info, plus contains a flag 
    /// indicating if subscribed or not and the reader writer lock 
    /// for when the subscription is read or updated.
    /// </summary>
    /// <typeparam name="TArgs">Event args</typeparam>
    internal class SafeHandlerInfo< TArgs> where TArgs : EventArgs 
    {
        public EventHandler<TArgs> Handler;
        public bool Subscribed = true;
        //public object LockObj = new object();

        public ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

        public SafeHandlerInfo(EventHandler<TArgs> handler)
        {
            Handler = handler;
        }

    }

    /// <summary>
    /// SafeEvent class provides safety for unsubscribing from events 
    /// so even with multiple threads after unsubscribing it will not get called.
    /// Also makes sure that a null exception won't happen due to the removing of events
    /// </summary>
    /// <typeparam name="TArgs">The type of the event args</typeparam>
    public class SafeEventNS<TArgs>
        where TArgs : EventArgs
        
    {

        Dictionary<EventHandler<TArgs>, 
            SafeHandlerInfo<TArgs>> _handlers = new Dictionary<EventHandler<TArgs>, 
            SafeHandlerInfo<TArgs>>();
        private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();

        public event EventHandler<TArgs> Event {
            add
            {
                Subscribe(value);
            }

            remove
            {
                Unsubscribe(value);
            }
            
        }       

        /// <summary>
        /// Used to fire this event from within the class using the SafeEvent
        /// This reads a copy of the list and calls all of the subscribers in it that are still subscribed.
        /// Anything that was unsubscribed gets removed at the end of the event call.  It was handler here since
        /// The copy of the list might be held by multiple threads unsubscribe flags a handler unsubscribed and removes it.
        /// that way if it is still in the list it will not be called
        /// 
        /// </summary>
        /// <param name="args">The event args</param>
        public virtual void FireEvent(object sender,TArgs args)
        {
            _rwLock.EnterReadLock();
            List<SafeHandlerInfo<TArgs>> localHandlerInfos = _handlers.Values.ToList();
            _rwLock.ExitReadLock();

            foreach (SafeHandlerInfo<TArgs> info in localHandlerInfos)
            {
                info.Lock.EnterReadLock();
                try
                {
                    if (info.Subscribed)
                    {
                        EventHandler<TArgs> handler = info.Handler;
                        try
                        {
                            handler(sender, args);
                        }
                        catch { };
                    }
                    
                }
                finally
                {
                    info.Lock.ExitReadLock();
                }
            }

        }


        /// <summary>
        /// Unsubscribe - internally used to unsubscribe a handler from the event
        /// </summary>
        /// <param name="unsubscribeHandler">The handler being unsubscribed</param>
        protected void Unsubscribe(EventHandler<TArgs> unsubscribeHandler)
        {

            _rwLock.EnterWriteLock();
            try
            {

                SafeHandlerInfo<TArgs> handler = null;

                if (_handlers.TryGetValue(unsubscribeHandler, out handler))
                {
                    handler.Lock.EnterWriteLock();
                    try
                    {

                        handler.Subscribed = false;
                        
                        _handlers.Remove(handler.Handler);  
                        
                    }
                    finally
                    {
                        handler.Lock.ExitWriteLock();
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }

        }

        /// <summary>
        /// Subscribe - Called to subscribe the handler
        /// </summary>
        /// <param name="eventHandler">The handler to subscribe</param>
        protected void Subscribe(EventHandler<TArgs> eventHandler)
        {

            _rwLock.EnterWriteLock();
            try
            {

                SafeHandlerInfo<TArgs> handlerInfo = null;

                if (!_handlers.TryGetValue(eventHandler, out handlerInfo))
                {
                    handlerInfo = new SafeHandlerInfo<TArgs>(eventHandler);


                    handlerInfo.Lock.EnterWriteLock();
                    try
                    {

                        _handlers.Add(eventHandler, handlerInfo);
                    }
                    finally
                    {
                        handlerInfo.Lock.ExitWriteLock();
                    }
                }
                else
                {
                    handlerInfo.Lock.EnterWriteLock();
                    try
                    {

                        handlerInfo.Subscribed = true;
                    }
                    finally
                    {
                        handlerInfo.Lock.ExitWriteLock();
                    }

                }
            }
            catch (Exception e)
            {
                throw e;
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }

        }      

    }

Let’s show an example of how to use this, which is the same way as the previous SafeEvent class.

/// <summary>
    /// TestEventUser
    /// Test the SafeEvent class for implementing an event using SafeEvent
    /// </summary>
    public class TestSafeEventNS
    {
        //declare our private event 
        private SafeEventNS<EventArgs> _myEvent = new SafeEventNS<EventArgs>();

        /// <summary>
        /// MyEvent - proxy the add/remove calls to the private event
        /// </summary>
        public event EventHandler<EventArgs> MyEvent  
        {
            add
            {
                _myEvent.Event += value;
            }

            remove
            {
                _myEvent.Event -= value;

            }

        }

        /// <summary>
        /// OnMyEvent - standard example idiom of how to fire an event
        /// </summary>
        /// <param name="args"></param>
        protected void OnMyEvent(EventArgs args)
        {

            _myEvent.FireEvent(this,args); //call our private event to fire it
        }

        /// <summary>
        /// FireEvent - This we provided on our test class as a quick way to fire the event from another class.
        /// </summary>
        public void FireEvent()
        {
            OnMyEvent(new EventArgs());
        }

    }

SafeEventNS Solution Summary

The SafeEventNS class still suffers from the potential for deadlocks, but offers the benefit of true multithreaded event support, if needed.

Problems  
#2 Null exception race condition  
#3 A new subscriber might not get called immediately  
#4 A handler is still called after unsubscribe.  
#5 Possible deadlock X
#6 might not get most recent copy of handlers to call  
#7 Problem/Feature - Events could be fired to delegates out of sequence X

Reminder Problem #7 has to be handled by the publisher of the event only publishing in the correct sequence using multithreaded synchronization.

Another way, single threaded access

So as mentioned in the code project article and the original author John Skeet, there is sort of a way to solve many of these problems, which is don’t subscribe/unsubscribe or fire the events from multiple threads, only use one thread. That will avoid the locking overhead and some of the complexity and solves all of the problems (sort of). It does add some complexity on the threading model. Also it is arguable as to if it solved #3 and #4 problems as if subscription is put into a queue to be handled on another thread, then you don’t know when you will finally be subscribed or unsubscribed. If you built an asynchronous notification to when you were subscribed or unsubscribed that might be the one way to try to solve that.

For other potential implementations take a look at the previously mentioned article mentioned it explains the implementations and the problems each of them has:

http://www.codeproject.com/Articles/37474/Threadsafe-Events

Solving Problem #7

As mentioned this problem is when events can be fired out of sequence. To solve that the source of events must be controlled, so if there is a required sequencing of events you protect your state machine and the firing of those events with synchronization if multiple threads could be firing it.

    /// <summary>
    /// TestEventUseSolveProblemSeven
    /// Test the SafeEvent class for implementing an event using 
    /// SafeEvent &ndash; we want StartEvent to be fired before EndEvent
    /// This shows how to solve problem #7.  If FireEvent() is that 
    /// only thing that could be used to fire events then
    /// that will guarantee a StartEvent is called before an EndEvent()
    /// </summary>
    public class TestEventUseSolveProblemSeven
    {
        //declare our private event 
        private SafeEvent<EventArgs> _startEvent = new SafeEvent<EventArgs>();
        private SafeEvent<EventArgs> _endEvent = new SafeEvent<EventArgs>();
        private object _lock = new object();
        bool _started = false;

        /// <summary>
        /// StartEvent - proxy the add/remove calls to the private event
        /// </summary>
        public event EventHandler<EventArgs> StartEvent
        {
            add
            {
                _startEvent.Event += value;
            }

            remove
            {
                _startEvent.Event -= value;

            }

        }

        public event EventHandler<EventArgs> EndEvent
        {
            add
            {
                _endEvent.Event += value;
            }

            remove
            {
                _endEvent.Event -= value;

            }

        }

        /// <summary>
        /// OnStartEvent - standard example idiom of how to fire an event
        /// </summary>
        /// <param name="args"></param>
        protected void OnStartEvent(EventArgs args)
        {

            _startEvent.FireEvent(this, args); //call our private event to fire it
        }

        /// <summary>
        /// OnEndEvent - standard example idiom of how to fire an event
        /// </summary>
        /// <param name="args"></param>
        protected void OnEndEvent(EventArgs args)
        {

            _endEvent.FireEvent(this, args); //call our private event to fire it
        }

        /// <summary>
        /// FireEvent - This we provided on our test class as a quick way to fire 
        /// the event from another class.
        /// </summary>
        public void FireEvent()
        { 
            // by using a lock to prevent other events from being fired and managing our 
            // state we can guarantee event ordering.
            lock (_lock)  
            {
                if (_started)
                    OnEndEvent(new EventArgs());
                else
                    OnStartEvent(new EventArgs());
                _started = !_started;

            }
        }

    }

Summary

So as we have seen there is no straightforward easy answer to all multithreading issues. So you will need to pick the best answer for your purpose/requirements and handle the scenarios that can occur properly to avoid event/multithreading issues.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
上传项目不支持Firefox,提示代码附件太大(1.4M),我写了30多分钟的描述全没了,太坑爹了。 10分有点贵,绝对原创,共2个代码文件300多行,下载请谨慎。你下载了,若绝对不爽在评论中说出来,不要让其他同学上当,如果觉得还可以也请留言。 代码采用多工作者多线程执行任务。通过暴露的方法往工作者传递消息,然后采用事件回调返回处理结果,实现的事件有OnThreadComplete,OnAddedTask,OnStart,OnSuccess,OnFailure,OnTimeout。 事件回调支持同步或异步,每工作者可以指定执行超时时间,避免线程阻塞死掉。队列采用线程安全的BlockingCollection,每组工作者用一个队列。委托采用Func来定义的,没有采用传统且不太好理解的Delegate。这让代码减少很多,也更容易理解。多线程应该采用消息中心来交换数据,这样就规避了线程同步交互,等待,阻塞等等,全部是异步调用,全部是接收消息工作,然后产生消息,线程间没有耦合,消息中心有很多成熟的方案如RabbitMQ, Redis(里面有简单的消息交换),微软有消息云服务等。如果应用不复杂,可以采用DB做个简单的消息中心,建议采用HTTP接口来获取与写入消息,方便将来升级重构消息中心。 开发环境VS2012,Framework4.0,代码注释量很大,如果你高兴这代码你可以随意蹂躏,如果你有建设性意见请告诉我。 下面是部分测试代码: //发送消息方法容器 var msgContainer = new Hashtable(); //创建并启动工作者 foreach (var key in workers.Keys) { //创建工作者 //启动5个线程,异步事件回调,方法执行20秒超时,程序跑起来有100个线程,由于引入超时控制,实际线程将达100+50 //下面的20个工作组,有5个是超时的,主要测试OnTimeout事件,你可以设置seleep的时间来控制 //我把sleep的时间设置的有点长,方便你测试 //测试的时候你会看见有异常,那是应为Timeout我采用的是Thread.Abort方法,这样才出发了ontimeout事件 var worker = new Sehui.Worker(5, key.ToString(), (Func)workers[key], false, new TimeSpan(0, 0, 20)); worker.OnStart += worker_OnEvent; worker.OnSuccess += worker_OnEvent; worker.OnFailure += worker_OnEvent; worker.OnTimeout += worker_OnEvent; //启动工作者 worker.Start(); //将增加消息方法放到Hashtable中 //这里我是偷懒,下面可以用循环的方式往线程中add message msgContainer.Add(key.ToString(), new Func(worker.AddTask)); } //向20个工作者发送消息,每个工作者发送20条消息 for (var i = 0; i < 20; i++) { for (var k = 0; k < 20; k++) { ((Func)msgContainer["SyncDb" + k])("[Work " + k + "] Message " + i); Console.WriteLine("send msg to worker{0},msgid:{1}", k, i); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值