继MSMQ简单包装类后,又把MSMQ再更新了一下。主要加入一些事件,有消息到达时,可用外部事件处理(ProcessMessageHandler),以及消息格式不是预期格式时的事件(InvalidTypeHandler),设置接收超时时间(Timeout);MSMQ里没有消息或接收超时的处理事件(NoMessageOrTimeoutHandler);可以设置接受到消息后是同步执行(ThreadCount = 0时)还是异步执行(ThreadCount > 0时);并可以限制多线程执行时的数量(ThreadCount = n);
使用MSMQ的准备工作参见http://blog.csdn.net/iwteih/archive/2008/09/05/2884773.aspx
先上接口:
- namespace ×××.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 ×××.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 ×××.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时会出现消息丢失现象。