继MSMQ简单包装类后,又把MSMQ再更新了一下。主要加入一些事件,有消息到达时,可用外部事件处理(ProcessMessageHandler),以及消息格式不是预期格式时的事件(InvalidTypeHandler),设置接收超时时间(Timeout);MSMQ里没有消息或接收超时的处理事件(NoMessageOrTimeoutHandler);可以设置接受到消息后是同步执行(ThreadCount = 0时)还是异步执行(ThreadCount > 0时);并可以限制多线程执行时的数量(ThreadCount = n);
使用MSMQ的准备工作参见http://www.cnblogs.com/iwteih/archive/2010/01/18/1650399.html
先上接口:
namespace XXX.Msmq { public interface IMsmq<T> : IDisposable { /// <summary> /// Serializes and deserializes objects to or from the body of a message /// </summary> MessageFormatter Formatter { get; set; } /// <summary> /// The count of threads to process object in queue. /// If omitted or 0, synchronous execution, otherwise asynchronous execution. /// </summary> int ThreadCount { get; set; } /// <summary> /// The time to be spent to get a message from Msmq. /// If omitted , the hook will be alive all the time /// </summary> TimeSpan Timeout { get; set; } /// <summary> /// Push an object into MSMQ /// </summary> /// <param name="element">The object to be pushed into MSMQ</param> void Push(T element); /// <summary> /// Pop the element in MSMQ /// </summary> /// <returns>object in msmq</returns> T Pop(); /// <summary> /// Start to listen Msmq /// </summary> void StartListener(); /// <summary> /// Stop listening Msmq. Make sure that StopListen & StartListen are paired matched /// or no StopListen. /// </summary> void StopListener(); /// <summary> /// The real funtion to process messsage. /// </summary> event ProcessMessageHandler ProcessMessage; /// <summary> /// Triggered when no message in MSMQ or timeout /// </summary> event NoMessageOrTimeoutHandler ProcessNoMessageOrTimeout; /// <summary> /// If message type is expected, use this event for handling /// </summary> event InvalidTypeHandler ProcessInvalidType; } }
实现如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Messaging; using System.Threading; using System.Runtime.InteropServices; using log4net; namespace XXX.Msmq { public enum MessageFormatter { XmlMessageFormatter, BinaryMessageFormatter, } public class MessageArgs : EventArgs { /// <summary> /// The object poped from queue /// </summary> public object ObjectToProcess { get; set; } } public delegate void ProcessMessageHandler(object sender, MessageArgs args); public delegate void NoMessageOrTimeoutHandler(object sender, EventArgs args); public delegate void InvalidTypeHandler(object sender, EventArgs args); internal class Msmq<T> : IMsmq<T> { private static readonly ILog logger = LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); MessageQueue queue = null; int upperLimit = 0; int currentThreadCount = 0; #region Constructor /// <summary> /// Initial a Msmq object /// </summary> /// <param name="qName">Queue name</param> public Msmq(string qName) : this(qName, 0) { } /// <summary> /// Initial a Msmq object. /// </summary> /// <param name="qName">Queue name</param> /// <param name="limit">Upper limit in this msmq</param> public Msmq(string qName, int limit) { if (!string.IsNullOrEmpty(qName) && !MessageQueue.Exists(qName)) { MessageQueue.Create(qName); } queue = new MessageQueue(qName); upperLimit = limit; } #endregion #region IMsmq<T> Members IMessageFormatter msgFormatter = new XmlMessageFormatter(new Type[] { typeof(T) }); private MessageFormatter formatter; /// <summary> /// Serializes and deserializes objects to or from the body of a message /// </summary> MessageFormatter IMsmq<T>.Formatter { get { return formatter; } set { formatter = value; if (formatter == MessageFormatter.BinaryMessageFormatter) { msgFormatter = new BinaryMessageFormatter(); } else { msgFormatter = new XmlMessageFormatter(new Type[] { typeof(T) }); } } } /// <summary> /// How many threads to process message. If omitted or 0, synchronous execution, otherwise asynchronous execution /// </summary> private int threadCount = 0; int IMsmq<T>.ThreadCount { get { return threadCount; } set { threadCount = value; if (threadCount < 0) threadCount = 0; } } /// <summary> /// The time to wait while trying to get an object from Msmq. /// </summary> private TimeSpan timeout = TimeSpan.MaxValue; TimeSpan IMsmq<T>.Timeout { get { return timeout; } set { timeout = value; } } private event ProcessMessageHandler internelProcessMessage; /// <summary> /// The real funtion to process messsage. /// </summary> //public event ProcessMessageHandler ProcessMessage; event ProcessMessageHandler IMsmq<T>.ProcessMessage { add { internelProcessMessage += value; } remove { internelProcessMessage -= value; } } private event NoMessageOrTimeoutHandler noMessageOrTimeoutHandler; /// <summary> /// Triggered when no message in MSMQ or timeout /// </summary> event NoMessageOrTimeoutHandler IMsmq<T>.ProcessNoMessageOrTimeout { add { noMessageOrTimeoutHandler += value; } remove { noMessageOrTimeoutHandler -= value; } } private event InvalidTypeHandler invalidTypeHandler; /// <summary> /// If message type is expected, use this event for handling /// </summary> event InvalidTypeHandler IMsmq<T>.ProcessInvalidType { add { invalidTypeHandler += value; } remove { invalidTypeHandler -= value; } } /// <summary> /// Push an object into MSMQ /// </summary> /// <param name="element">The object to be pushed into MSMQ</param> void IMsmq<T>.Push(T element) { Send(element); } void Send(object element) { using (System.Messaging.Message message = new System.Messaging.Message()) { message.Body = element; message.Formatter = msgFormatter; queue.Send(message); } //In original status, MSMQ is empty, error will throw when calling CurrentCount. Because queue is not open. //so i allow insert action happen before calling CurrentCount. //If reach the upper message limit number, sleep for one minute. while (upperLimit != 0 && (CurrentMessageCount >= upperLimit)) { System.Threading.Thread.Sleep(60000); } } /// <summary> /// Pop the element in MSMQ /// </summary> /// <returns>object in msmq</returns> T IMsmq<T>.Pop() { return Receive(); } T Receive() { T element = default(T); try { using (Message message = queue.Receive(new TimeSpan(0, 0, 10))) { message.Formatter = msgFormatter; element = (T)message.Body; } } catch (MessageQueueException mqex) { //Ingore the exception when queue is empty if (mqex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout) { logger.Error(mqex); } } return element; } /// <summary> /// Start to listen Msmq /// </summary> void IMsmq<T>.StartListener() { queue.ReceiveCompleted += new ReceiveCompletedEventHandler(queue_ReceiveCompleted); BeginReceiveMessage(); } /// <summary> /// Stop listening Msmq. Make sure that StopListen & StartListen are paired matched /// or no StopListen. /// </summary> void IMsmq<T>.StopListener() { queue.ReceiveCompleted -= new ReceiveCompletedEventHandler(queue_ReceiveCompleted); currentThreadCount = 0; } #endregion void BeginReceiveMessage() { queue.BeginReceive(timeout); } void queue_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e) { Message msg = null; MessageStatus status = MessageStatus.Unknown; try { msg = ((MessageQueue)sender).EndReceive(e.AsyncResult); status = MessageStatus.OK; } catch (MessageQueueException qexp) { status = MessageStatus.QueueError; if (qexp.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) { status = MessageStatus.IOTimeout; } else { logger.Error(qexp); } } catch (Exception exp) { logger.Error(exp); } //Handle message in exception condition if (msg == null) { switch (status) { case MessageStatus.IOTimeout: if (noMessageOrTimeoutHandler != null) { noMessageOrTimeoutHandler(this, null); } break; default: break; } BeginReceiveMessage(); return; } msg.Formatter = msgFormatter; if (msg.Body is T) { T element = (T)msg.Body; if (internelProcessMessage != null) { //asynchronously execute if (threadCount > 0) { Action<T> callback = RunAsnyc; callback.BeginInvoke(element, AfterRunAsnyc, null); //control the multi-thread work flow to make sure only specified threads are working while (true) { if (currentThreadCount >= threadCount) { Thread.Sleep(1000); } else { Interlocked.Increment(ref currentThreadCount); BeginReceiveMessage(); } } } // synchronously execute else { internelProcessMessage(this, new MessageArgs { ObjectToProcess = element }); BeginReceiveMessage(); } } } else { status = Msmq<T>.MessageStatus.InvalidType; //If invalidTypeHandler defined, using it otherwise resend message into Msmq if (invalidTypeHandler != null) { invalidTypeHandler(msg, null); } else { Send(msg); } } msg.Dispose(); } void AfterRunAsnyc(IAsyncResult itfAR) { Interlocked.Decrement(ref currentThreadCount); } private void RunAsnyc(T element) { foreach (var v in internelProcessMessage.GetInvocationList()) { ProcessMessageHandler pmh = (ProcessMessageHandler)v; pmh.Invoke(this, new MessageArgs { ObjectToProcess = element }); } } /// <summary> /// This works well for the situation that MSMQ and KEXQueue is on the same machine. /// I am not sure it can work well if the two are separated. /// </summary> private int CurrentMessageCount { get { //MSMQ.MSMQManagement msmq = new MSMQ.MSMQManagement(); object server = null; object path = queue.Path; object format = null; //msmq.Init(ref server, ref path, ref format); //int count = msmq.MessageCount; //Marshal.ReleaseComObject(msmq); //return count; return 0; } } enum MessageStatus { /// <summary> /// Message received successfully /// </summary> OK, /// <summary> /// Queue is empty or occur when time out /// </summary> IOTimeout, /// <summary> /// Cannot convert the messsage to expected object type /// </summary> InvalidType, /// <summary> /// Exception occurs when receiving the message /// </summary> QueueError, /// <summary> /// Exception thrown not by Msmq /// </summary> Unknown } #region IDisposable Members void IDisposable.Dispose() { if (queue != null) queue.Dispose(); } #endregion } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace XXX.Msmq { public class MsmqFactory<T> { private static object objLock = new object(); private static Dictionary<string, IMsmq<T>> queuelist = new Dictionary<string, IMsmq<T>>(); /// <summary> /// Create a Msmq instance. /// </summary> /// <param name="queueName">Queue name</param> /// <returns>A Msmq instance</returns> /// <remarks>If calling CreateMsmq twice. The two returned objects are separated instances. /// For example, object one = CreateMsmq("A"); object two = CreateMsmq("A"); /// one and two are different objecct. /// </remarks> public static IMsmq<T> CreateMsmq(string queueName) { return new Msmq<T>(queueName); } /// <summary> /// Create a Msmq instance with message count limitation. /// </summary> /// <param name="queueName">Queue name</param> /// <param name="limit">Upper limit in this msmq</param> /// <returns>A Msmq instance</returns> /// <remarks>If calling CreateMsmq twice. The two returned objects are separated instances. /// For example, object one = CreateMsmq("A"); object two = CreateMsmq("A"); /// one and two are different objecct. /// </remarks> public static IMsmq<T> CreateMsmq(string queueName, int limit) { return new Msmq<T>(queueName, limit); } /// <summary> /// Initial a singleton Msmq object /// </summary> /// <param name="qName">Queue name</param> /// <returns>A Msmq instance</returns> /// <remarks>If calling CreateMsmq twice. The two returned objects are the same instances if their queue names are the same.. /// For example, object one = CreateMsmq("A"); object two = CreateMsmq("A"); /// one and two are the same object because they have the same queuename. /// </remarks> public static IMsmq<T> CreateSingletonMsmq(string queueName) { return CreateSingletonMsmq(queueName, 0); } /// <summary> /// Initial a singleton Msmq object /// </summary> /// <param name="qName">Queue name</param> /// <param name="limit">Upper limit in this msmq</param> /// <returns>A Msmq instance</returns> /// <remarks>If calling CreateMsmq twice. The two returned objects are the same instances if their queue names are the same. /// For example, object one = CreateMsmq("A"); object two = CreateMsmq("A"); /// one and two are the same object because they have the same queuename. /// </remarks> public static IMsmq<T> CreateSingletonMsmq(string queueName, int limit) { lock (objLock) { if (!queuelist.ContainsKey(queueName)) { Msmq<T> queue = new Msmq<T>(queueName); queuelist.Add(queueName, queue); return queue; } else { return queuelist[queueName]; } } } /// <summary> /// Dispose a queue by given queuename. /// </summary> /// <param name="qName">The queue with the name to be disposed </param> public static void DisposeQueue(string queueName) { lock (objLock) { if (queuelist.ContainsKey(queueName)) { queuelist[queueName].Dispose(); queuelist.Remove(queueName); } } } /// <summary> /// Dispose all queues. /// </summary> public static void DisposeQueue() { lock (objLock) { foreach (var queue in queuelist.Values) { queue.Dispose(); } queuelist.Clear(); } } } } 调用如下:
IMsmq<YourObj> msmq = MsmqFactory<YourObj>.CreateMsmq("QueueName"); msmq.Formatter = MessageFormatter.XmlMessageFormatter; msmq.Timeout = new TimeSpan(0, 0, 30); msmq.ProcessMessage += new ProcessMessageHandler(ProcessMessage); msmq.ProcessNoMessageOrTimeout += new NoMessageOrTimeoutHandler(ProcessNoMessageOrTimeout); msmq.StartListener();
MSMQ接收消息的方式有很多,比如BeginPeek或EndPeek,可以根据需要自行改动。
NOTE: 若将MSMQ定义成Transaction,则在多线程接收message时会出现消息丢失现象。