半同步半异步模式的实现 - MSMQ实现

所谓半同步半异步是指,在某个方法调用中,有些代码行是同步执行方式,有些代码行是异步执行方式,下面我们来举个例子,还是以经典的PlaceOrder来说,哈哈。

PlaceOrder的主要逻辑:

public bool PlaceOrder(OrderInfo order)
{
//验证Order合法性 //OrderInfo增加到仓储 //生成order的pdf //通知客户,email方式
}

我们假设做出如下决定:

public bool PlaceOrder(OrderInfo order)
{
//验证Order合法性 (同步执行) //OrderInfo增加到仓储 (异步执行,考虑到锁,要放到消息队列中,让后台Worker来执行具体的sql操作) //生成order的pdf (同步执行,其实也可以异步,此处demo意图,因此做成同步执行) //通知客户,email方式 (同步执行,同上)
}

如上面所示,如果我们只是在"OrderInfo增加到仓储"这里通过Async方式(无论是多线程,或者是msmq、rabbitq),如果只是触发这个异步执行,那么到函数返回时,很可能这个异步操作还没有执行完成,但是UI层(又或者其他函数)却需要某些信息,比如OrderID。因此,在这个函数中,除了发起异步调用外,还需要wait,直到异步调用执行完成,其实这个函数相当于完成了2个线程并行执行,但是最终返回的条件必须是2个线程都执行完成。

我们来看下

        EventWaitHandle signal = new EventWaitHandle(true, EventResetMode.ManualReset);   //多线程之间的阻塞机制,需要这个变量
OrderInfo returnedOrderInfo = null; //从多线程那边返回的变量会保存在这里
public bool PlaceOrder(OrderInfo order)
{
//验证Order合法性 (同步执行) //OrderInfo增加到仓储 (异步执行,考虑到锁,要放到消息队列中,让后台Worker来执行具体的sql操作)
string msgId=SendOrder2Queue(order); //发送order到msmq,并且获取相应的消息ID,因为后面会用到这个消息ID //生成order的pdf //会在这里阻塞,直到应答队列出现相应msgId的消息为止
signal.Reset(); //初始化变量
returnedOrderInfo = null; //初始化变量
ThreadPool.QueueUserWorkItem(new WaitCallback(CheckResponseQueue), msgId); //调用ms的ThreadPool进行多线程
signal.WaitOne(); //阻塞住,直到子线程发出Set命令 //通知客户,email方式 //收尾逻辑
if (this.returnedOrderInfo != null && !this.returnedOrderInfo.OrderID.Equals(Guid.Empty))
{
CloneOrder(order, this.returnedOrderInfo);
return true;
}
return false;
}

需要注意的地方是,发送email那里,必须放在最后面,想象下这种情况:插入数据库失败了,此时msmq的应答队列就会返回相应的失败消息,此时就需要根据结果来判断,是否需要发送email了(上面代码没有考虑到这点)。

考虑到断电等情况,需要将msmq创建为Transaction类型的queue,如下:

public static class Config
{
public static readonly string OrderQueueConnectionString = ".\\private$\\Order";
public static readonly string OrderResponseQueueConnectionString = ".\\private$\\OrderResponse";
public static void PrepareMSMQ()
{
if (MessageQueue.Exists(OrderQueueConnectionString))
MessageQueue.Delete(OrderQueueConnectionString);
if (MessageQueue.Exists(OrderResponseQueueConnectionString))
MessageQueue.Delete(OrderResponseQueueConnectionString); MessageQueue.Create(OrderQueueConnectionString, true); //true,代表Transaction的队列
MessageQueue.Create(OrderResponseQueueConnectionString, true); //同上
}
}

大家已经看到了,其实有2个队列需要建立,一个是发送队列,另外一个是应答队列;ThreadPool中的子线程就是用来Monitor这个应答队列中是否有相应消息的,我们来看看代码:

private void CheckResponseQueue(object state)
{
string msgId = (string)state;
string sMessageConnectionString_ResponseQueue = Config.OrderResponseQueueConnectionString;
MessageQueue mq_response = new MessageQueue(sMessageConnectionString_ResponseQueue);
mq_response.Formatter = new XmlMessageFormatter(new Type[] { typeof(OrderInfo) }); //这个很重要,要传输什么样的消息,就要写相应的格式化程序
while (true)
{
System.Threading.Thread.Sleep(200);
Message[] msgs = mq_response.GetAllMessages(); //这句会获取所有的消息,但是不会从队列中去掉,类似于Peek的效果 string foundMsgId = string.Empty;
foreach(Message msg in msgs)
{
if (msg.Label == msgId)
{
foundMsgId = msg.Id;
break;
}
}
if (foundMsgId != string.Empty)
{
Message msg=mq_response.ReceiveById(foundMsgId); //找到msgId后,这句才会真正从队列中移除消息
OrderInfo replyOrderInfo=(OrderInfo)msg.Body; returnedOrderInfo = new OrderInfo(); //赋值给返回变量
returnedOrderInfo.FirstName = replyOrderInfo.FirstName;
returnedOrderInfo.LastName = replyOrderInfo.LastName;
returnedOrderInfo.BuyWhat = replyOrderInfo.BuyWhat;
returnedOrderInfo.OrderID = replyOrderInfo.OrderID;
break;
}
}
signal.Set(); //发出信号,代表完成,让主线程继续往下执行
}

下面再来看看消息发送后,真正的后台处理程序(是个Console程序):

class Program
{
static void Main(string[] args)
{
System.Threading.Thread.Sleep(2000);
string msmq = Core.Config.OrderQueueConnectionString;
if (!MessageQueue.Exists(msmq))
{
Console.WriteLine("msmq not exist.");
return;
} MessageQueue mq = new MessageQueue(msmq);
MessageQueueTransaction tx = new MessageQueueTransaction(); //事务性队列必须用这个才能正确插入消息
mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(OrderInfo) });
while (true)
{
Message msg = mq.Receive();
Console.WriteLine("processing {0}", msg.Id);
OrderInfo order = (OrderInfo)msg.Body; order.FirstName +=", processed on "+DateTime.Now.ToString();
order.OrderID = Guid.NewGuid(); if (msg.ResponseQueue != null) //由于在发送消息的时候已经指定了应答队列,因此此处只是简单的判断这个属性就可以了
{
Message msg_reply = new Message();
msg_reply.Body = order;
msg_reply.Label = msg.Id;
msg_reply.Formatter = new System.Messaging.XmlMessageFormatter(new Type[] { typeof(OrderInfo) }); tx.Begin(); //很重要
msg.ResponseQueue.Send(msg_reply, tx);
tx.Commit(); //同上
}
Console.WriteLine("done");
}
}
}

我们再来看下主程序:

class Program
{
static void Main(string[] args)
{
Config.PrepareMSMQ(); //准备msmq资源,比如create等 OrderInfo order = new OrderInfo();
order.FirstName = "aaron";
order.LastName = "dai";
order.BuyWhat = "Car"; Console.WriteLine("Old OrderID--->" + order.OrderID);
Console.WriteLine("Old FirstName--->" + order.FirstName); OrderService srv = new OrderService();
bool success=srv.PlaceOrder(order); Console.WriteLine("*******************************");
Console.WriteLine("Processed OrderID--->" + order.OrderID);
Console.WriteLine("Processed FirstName--->"+order.FirstName);
Console.WriteLine("*******************************");
Console.ReadLine();
}
}

就要好了,来看看效果图:

Code

自省推动进步,视野决定未来。
心怀远大理想。
为了家庭幸福而努力。
用A2D科技,服务社会。
 

半同步半异步模式的实现 - MSMQ实现的更多相关文章

  1. 分布式缓存系统 Memcached 半同步/半异步模式

    在前面工作线程初始化的分析中讲到Memcached采用典型的Master_Worker模式,也即半同步/半异步的高效网络并发模式.其中主线程(异步线程)负责接收客户端连接,然后分发给工作线程,具体由工 ...

  2. 领导者/追随者(Leader/Followers)模型和半同步/半异步(half-sync/half-async)模型都是常用的客户-服务器编程模型

    领导者-追随者(Leader/Followers)模型的比喻 半同步/半异步模型和领导者/追随者模型的区别: 半同步/半异步模型拥有一个显式的待处理事件队列,而领导者-追随者模型没有一个显式的队列(很 ...

  3. 使用C++11 开发一个半同步半异步线程池

    摘自:<深入应用C++11>第九章 实际中,主要有两种方法处理大量的并发任务,一种是一个请求由系统产生一个相应的处理请求的线程(一对一) 另外一种是系统预先生成一些用于处理请求的进程,当请 ...

  4. 使用C++11实现一个半同步半异步线程池

    前言 C++11之前我们使用线程需要系统提供API.posix线程库或者使用boost提供的线程库,C++11后就加入了跨平台的线程类std::thread,线程同步相关类std::mutex.std ...

  5. c++11 实现半同步半异步线程池

    感受: 随着深入学习,现代c++给我带来越来越多的惊喜- c++真的变强大了. 半同步半异步线程池: 事实上非常好理解.分为三层 同步层:通过IO复用或者其它多线程多进程等不断的将待处理事件加入到队列 ...

  6. (原创)C++半同步半异步线程池2

    (原创)C++半同步半异步线程池 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 线程池可以高效的处理任务,线程池中开启多个线程,等待同步队列中的任务到来,任务到来多个线程 ...

  7. Half Sync And Half Async 半同步半异步模式

    如题,这是一个典型的CS结构的模式,至少曾经在CS结构中用过,只是没用好.当年用UDP死活都处理不过来网络命令,用此模式轻松解决. 此模式是典型的三层架构,同步层在一个线程内处理业务逻辑:异步层接受并 ...

  8. C++11 半同步半异步线程池的实现

    #include <list> #include <mutex> #include <thread> #include <condition_variable ...

  9. C++ 半同步半异步的任务队列

    代码已发布至 HAsyncTaskQueue

随机推荐

  1. 2014牡丹江区域赛H(特里)ZOJ3826

    Hierarchical Notation Time Limit: 2 Seconds      Memory Limit: 131072 KB In Marjar University, stude ...

  2. hadoop 2.2.0集群安装

    相关阅读: hbase 0.98.1集群安装 本文将基于hadoop 2.2.0解说其在linux集群上的安装方法,并对一些重要的设置项进行解释,本文原文链接:http://blog.csdn.net ...

  3. IOS开发中绘制地图线路

    地图应用经常会涉及到线路的绘制问题,ios下可以使用MKMapView进行地图开发,使用 MKOverlayView进行线路的绘制. 使用MKMapView添加MKMap.framework 和Cor ...

  4. C语言库函数大全及应用实例十二

    原文:C语言库函数大全及应用实例十二                                          [编程资料]C语言库函数大全及应用实例十二 函数名: setrgbpalette ...

  5. navigator,JS检测浏览器插件

    最早由Netscape Navigator 2.0引入的navigator对象,现在已经成为识别客户端浏览器的事实标准.虽然其它浏览器也通过其它方式提供了相同或相似的信息(例如,IE中的window. ...

  6. 苹果iOS苹果公司的手机用户都有权索赔

    大家知道.手机中的操作系统(基础软件)存储在手机固(firm,ware)之中,一般而言,手机用户自己是不能修改的. 苹果iOS手机的系统后门(服务程序)也存储在手机固件之中.手机用户自己是无法删除的. ...

  7. JS document 获取 html对象的问题

    在了解document.getElementById()方法的时候,没有留意到被获取的对象的声明时的位置, 一个很基础很细节的问题. 比如说 这个js的引入位置: -----------------a ...

  8. Asterisk 未来之路3.0_0005

    原文:Asterisk 未来之路3.0_0005 第二章: Asterisk的架构   Asterisk 和其他众多传统的PBX是有区别的,拨号方案针对各种通道处理本质上采用同一种方式. 在传统的PB ...

  9. 在Ubuntu中编译QT工程Tesful

    今天晚上开机到Ubuntu中了,试了一下之前在Windows下建立的Tesful工程,发现没有任何改动就可以编译成功/运行. 附上图:

  10. 我的Android 4 学习系列之使用 Internet 资源

    目录 连接Internet资源 分析XML资源 使用Download Manager下载文件 查询Download manager 使用Account Manager 对 Google App Eng ...