WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)

对于一般的多线程操作,比如异步地进行基于文件系统的IO操作;异步地调用Web Service;或者是异步地进行数据库访问等等,是和具体的线程无关的。也就是说,对于这些操作,任意创建一个新的线程来执行都是等效的。但是有些情况下,有些操作却只能在固定的线程下执行。比如,在GUI应用下,对控件的访问就需要在创建该控件的线程下执行;或者我们在某个固定的线程中通过TLS(Thread Local Storage)设置了一些Context信息,供具体的操作使用,我们把操作和某个固定的线程的依赖称为线程关联性(Thread Affinity)。在这种情况下,我们的异步操作就需要被Marshal到固定的线程执行。在WCF并发或者Callback的情况下也具有这样的基于线程关联性的问题。

一、从基于Windows Application客户端的WCF回调失败谈起

在"我的WCF之旅"系列文章中,有一篇(WinForm Application中调用Duplex Service出现TimeoutException的原因和解决方案)专门介绍在一个Windows Application客户端应用, 通过WCF 的Duplex通信方式进行回调失败的文章.我们今天以此作为出发点介绍WCF在Thread Affinity下的表现和解决方案.

我们来创建一个WCF的应用来模拟该场景: 客户端是一个基于Windows Form应用, 完成一个计算器的功能, 用户输入操作数,点击"计算"按钮, 后台通过调用WCF service, 并传递一个用于显示计算结果的Callback对象; service进行相应的计算得到最后的运算结果,调用该Callback对象将运算结果显示到客户端界面.这是我们的WCF四层结构:

image

1、Contract:ICalculate & ICalculateCallback

   1: namespace Artech.ThreadAffinity.Contracts
   2: {
   3:     [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
   4:     public interface ICalculate
   5:     {
   6:         [OperationContract]
   7:         void Add(double op1, double op2);
   8:     }
   9: } 

这是Service Contract,下面是Callback Contract,用于显示运算结果:

   1: namespace Artech.ThreadAffinity.Contracts
   2: {
   3:    public interface ICalculateCallback
   4:     {
   5:         [OperationContract]
   6:         void DisplayResult(double result);
   7:     }
   8: } 

2、Service:CalculateService

   1: namespace Artech.ThreadAffinity.Services
   2: {
   3:     [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
   4:     public class CalculateService:ICalculate
   5:     {
   6:         public static ListBox DisplayPanel
   7:         { get; set; }         
   8:  
   9:         #region ICalculate Members 
  10:  
  11:         public void Add(double op1, double op2)
  12:         {
  13:             double result = op1 + op2;
  14:             ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();            
  15:  
  16: DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
  17:  
  18:             callback.DisplayResult(result);
  19:         } 
  20:  
  21:         #endregion
  22:     }
  23: }


由于需要进行callback, 我们把ConcurrencyMode 设为Reentrant。当得到运算的结果后,通过OperationContext.Current.GetCallbackChannel得到callback对象,并调用之。还有一点需要提的是,该service是通过一个Windows Form application进行host的。并且有一个ListBox列出所有service执行的结果,就像这样:

image

3、Hosting

Hosting的代码写在Form的Load事件中:

   1: private void HostForm_Load(object sender, EventArgs e)
   2: {    
   3:     this._serviceHost = new ServiceHost(typeof(CalculateService));
   4:     CalculateService.DisplayPanel = this.listBoxResult;
   5:     CalculateService.SynchronizationContext = SynchronizationContext.Current;
   6:     this._serviceHost.Opened += delegate
   7:     { 
   8: this.Text = "The calculate service has been started up!";
   9:     }; 
  10:  
  11:     this._serviceHost.Open();
  12: } 

我们注意到了CalculateService使用到的用于显示所有预算结果的ListBox就是在这了通过static property传递的。

这么配置文件

   1: <configuration>
   2:     <system.serviceModel>
   3:         <services>
   4:             <service name="Artech.ThreadAffinity.Services.CalculateService">
   5:                 <endpoint binding="netTcpBinding" bindingConfiguration="" contract="Artech.ThreadAffinity.Contracts.ICalculate" />
   6:                 <host>
   7:                     <baseAddresses>
   8:                         <add baseAddress="net.tcp://127.0.0.1:8888/calculateservice" />
   9:                     </baseAddresses>
  10:                 </host>
  11:             </service>
  12:         </services>
  13:     </system.serviceModel>
  14: </configuration> 

4、Client

Client的界面很简单:输入两个操作数,点击“=”按钮,将运算结果显示出来。

image

先来看看client端对callback contract的实现:

   1: namespace Clients
   2: {
   3:     public class CalculateCallback : ICalculateCallback
   4:     {
   5:         public static TextBox ResultPanel;               
   6:  
   7:         #region ICalculateCallback Members 
   8:  
   9:         public void DisplayResult(double result)
  10:         {
  11:             ResultPanel.Text = result.ToString();
  12:         }         
  13:  
  14:         #endregion
  15:     }
  16: } 

这是配置:

   1: <configuration>
   2:     <system.serviceModel>
   3:         <client>
   4:             <endpoint address="net.tcp://127.0.0.1:8888/calculateservice"
   5:                 binding="netTcpBinding" bindingConfiguration="" contract="Artech.ThreadAffinity.Contracts.ICalculate"
   6:                 name="calculateservice" />
   7:         </client>
   8:     </system.serviceModel>
   9: </configuration>
然后是我们“=”按钮的单击事件对运算的实现:
   1: private void buttonCalculate_Click(object sender, EventArgs e)
   2: {
   3:     CalculateCallback.ResultPanel = this.textBoxResult;
   4:     DuplexChannelFactory<ICalculate> channelFactory = new DuplexChannelFactory<ICalculate>(new CalculateCallback(), "calculateservice");
   5:     ICalculate calculator = channelFactory.CreateChannel();
   6:     calculator.Add(double.Parse(this.textBoxOp1.Text), double.Parse(this.textBoxOp2.Text));
   7: } 

CalculateCallback 用于显示运算结果的TextBox通过statis property实现传递。这个实现很简单,貌似没有什么问题,但是我们运行程序,在客户端就会抛出这样的exception。可以看出是一个TimeoutException。

image

二、是什么导致TimeoutException?

我们现在来分析是什么导致了TimeoutException的抛出。原因很简单:由于我们对service的调用的是在UI 线程调用的,所以在开始调用到最终得到结果,这个UI Thread会被锁住;但是当service进行了相应的运算的到运算的结果后,需要调用callback对象对client进行回调,默认的情况下,Callback的执行是在UI线程执行的。当Callback试图执行的时候,发现UI 线程被锁,只能等待。这样形成一个死锁,UI线程需要等待CalculateService执行返回后才能解锁,而CalculateService需要Callback执行完成;而Callback需要等到UI线程解锁才能执行。

基于上门的原因,我们有两种解决方案:

  • CalculateService不必等到Callback执行完成就返回,我们可以通过异步调用Callback。或者让Client异步方式调用CalculateService,以便及时释放UI线程,我们可以通过One-way的方式来进行service的调用。
  • 让Callback的执行不必绑定到UI线程

三、解决方案一:通过异步调用或者One-way回调

为了简单起见,我们通过ThreadPool实现了异步回调:

   1: public void Add(double op1, double op2)
   2: {
   3:     double result = op1 + op2;
   4:     ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();  
   5:  
   6:     ThreadPool.QueueUserWorkItem(delegate{ callback.DisplayResult(result); }, null);
   7: } 

这是一种方案,另一种是将Add操作设成One-way的:

   1: namespace Artech.ThreadAffinity.Contracts
   2: {
   3:     [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
   4:     public interface ICalculate
   5:     {
   6:         [OperationContract(IsOneWay = true)]
   7:         void Add(double op1, double op2);
   8:     }
   9: } 

这两种方案都可以解决问题。

四、方案二、通过解除Callback操作和UI线程的关联性

现在我们才进入我们今天讨论的主题:WCF并发操作的线程关联性问题。在这之前,我们需要了解一个重要的对象:SynchonizationContext(System.Threading.SynchronizationContext)。SynchonizationContext就是为了解决这种线程关联性问题而设计的。SynchonizationContext提供了两个主要的API将操作和对应的Thread关联:Post和Send。

   1: public virtual void Post(SendOrPostCallback d, object state) 
   2: public virtual void Send(SendOrPostCallback d, object state) 

Send和Post分别以同步和异步的方式将以Delegate表示的具体的操作和SynchonizationContext对象对应的Thread关联,而SendOrPostCallback delegate对象代表你需要的线程关联操作,state代表传入delegate的参数:

public delegate void SendOrPostCallback(object state);

对于某些具有线程关联的应用,比如Windows Form application,在程序启动的时候,会设置当前的SynchonizationContext对象(Windows Form application使用的是继承了SynchonizationContext的WindowsFormsSynchronizationContext :System.Windows.Forms.WindowsFormsSynchronizationContext)。当前SynchonizationContext被成功初始化后,你就可以通过SynchonizationContext的静态属性Current得到它。在你自己的应用中,如何有需要,你也可以自定义SynchonizationContext,并通过静态方法SetSynchronizationContext将其设置为current SynchronizationContext。

对应WCF来说,无论是host一个service,还是在调用service时制定callback,在默认的情况下,service和callback的操作将自动和当前的SynchonizationContext进行关联(如何有的话)。也就是说,如过我们的service被host到Windows Form application下,那么service的操作将在UI 线程下执行;同理,如何我们在一个Windows Forms UI线程下调用duplex service并制定callback,那么callback的最终执行将在UI线程。

关于WCF对线程关联性的控制,可以通过ServiceBehavior或者CallbackBehavior的UseSynchronizationContext属性进行设定,该属性默认为true,这正式WCF默认具有线程关联性的原因。

现在我们来实现我们的第二套方案:让Callback的执行不必绑定到UI线程。为此我们只需要加上如何的CallbackBehavior attribute就可以了。

   1: namespace Artech.ThreadAffinity.Clients
   2: {
   3:     [CallbackBehavior(UseSynchronizationContext = false)]
   4:     public class CalculateCallback : ICalculateCallback
   5:     {
   6:         public static TextBox ResultPanel; 
   7:  
   8:         #region ICalculateCallback Members 
   9:  
  10:         public void DisplayResult(double result)
  11:         {
  12:             ResultPanel.Text = result.ToString();
  13:  
  14:         } 
  15:  
  16:         #endregion
  17:     }
  18: }
  19:  

但是现在我们运行我们的程序,将会出现如下的InvalidOperation异常:

image

原因很简单,由于我们将callbaclk的UseSynchronizationContext 设置成false,那么callback的操作将不会再UI线程下执行。但是我们需要运算的结果输入到UI的TextBox上,对UI上控件的操作需要在UI线程上执行,显然会抛出异常了。

为了我们引入SynchonizationContext到CalculateCallback中:将SynchonizationContext定义成一个static属性,通过Post方法异步地实现对运算结果的显示。

   1: namespace Artech.ThreadAffinity.Clients
   2: {
   3:     [CallbackBehavior(UseSynchronizationContext = false)]
   4:     public class CalculateCallback : ICalculateCallback
   5:     {
   6:         public static TextBox ResultPanel;
   7:        public static SynchronizationContext SynchronizationContext; 
   8:  
   9:         #region ICalculateCallback Members 
  10:  
  11:         public void DisplayResult(double result)
  12:         {
  13:              SynchronizationContext.Post(delegate { ResultPanel.Text = result.ToString(); }, null);           
  14:         }        
  15:  
  16:         #endregion
  17:     }
  18: } 

SynchonizationContext在调用service的时候指定:

   1: private void buttonCalculate_Click(object sender, EventArgs e)
   2: {
   3:     CalculateCallback.ResultPanel = this.textBoxResult;
   4:     CalculateCallback.SynchronizationContext = SynchronizationContext.Current; 
   5:  
   6:     DuplexChannelFactory<ICalculate> channelFactory = new DuplexChannelFactory<ICalculate>(new CalculateCallback(), "calculateservice");
   7:     ICalculate calculator = channelFactory.CreateChannel();
   8:     calculator.Add(double.Parse(this.textBoxOp1.Text), double.Parse(this.textBoxOp2.Text));
   9: } 

现在我们程序能够正常运行了。

五、另一种可选方案:通过ISynchronizeInvoke的Invoke/BeginInvoke

熟悉Windows Form编程的读者应该都知道,WinForm空间的基类Control(System.Windows.Forms.Control)都实现了System.ComponentModel.ISynchronizeInvoke接口,而Control对ISynchronizeInvoke的实现就是为了解决Control的操作必须在创建Control线程的问题,ISynchronizeInvoke定义Invoke和BeginInvoke方法方面我们以同步或者异步的方式操作Control:

   1: public interface ISynchronizeInvoke
   2: {
   3:     // Methods
   4:     [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
   5:     IAsyncResult BeginInvoke(Delegate method, object[] args);
   6:     object EndInvoke(IAsyncResult result);
   7:     object Invoke(Delegate method, object[] args); 
   8:  
   9:     // Properties
  10:     bool InvokeRequired { get; }
  11: } 
  12:  

如何我们放弃基于SynchonizationContext的解决方案,我们也可以通过基于ISynchronizeInvoke的方式来解决这个问题。为此我们这样定义CalculateCallback:

   1: namespace Artech.ThreadAffinity.Clients
   2: {
   3:     [CallbackBehavior(UseSynchronizationContext = false)]
   4:     public class CalculateCallback : ICalculateCallback
   5:     {
   6:         public static TextBox ResultPanel;
   7:         public delegate void DisplayResultDelegate(TextBox resultPanel, double result); 
   8:  
   9:         #region ICalculateCallback Members 
  10:  
  11:         public void DisplayResult(double result)
  12:         {
  13:             DisplayResultDelegate displayResultDelegate = new DisplayResultDelegate(DisplayResult);
  14:            ResultPanel.BeginInvoke(displayResultDelegate, new object[] { ResultPanel, result });                   
  15:         } 
  16:  
  17:         private void DisplayResult(TextBox resultPanel, double result)
  18:         {
  19:             resultPanel.Text = result.ToString();
  20:         } 
  21:  
  22:         #endregion
  23:     }
  24: } 
  25:  

由于BeginInvoke方式只能接受一个具体的delegate对象(不能使用匿名方法),所以需要定义一个具体的Delegate(DisplayResultDelegate)和对应的方法(DisplayResult),参数通过一个object[]传入。

从本质上将,这两种方式的实现完全是一样的,如何你查看System.Windows.Forms.WindowsFormsSynchronizationContext的代码,你会发现其Send和Post方方法就是通过调用Invoke和BeginInvoke方式实现的。

六、Service Hosting的线程关联性

我们花了很多的精力介绍了WCF Duplex通信中Callback操作的线程关联性问题,实际上我们使用到更多的还是service操作的线程关联性问题。就以我们上面的程序为例,我们通过一个Windows Form application来host我们的service,并且要求service的运算结束后将结果输出到server端的Window form的ListBox中,对ListBox的操作肯定需要的Host程序的UI线程中执行。

按照我们一般的想法,我们的Service面向若干client,肯定是并发的接收client端的请求,以多线程的方式执行service的操作,那么操作中UI 控件的操作肯定会出现错误。

我们的程序依然可以正常运行,其根本原因是WCF的service操作默认实现了对Host service的当前线程的SynchonizationContext实现了关联。与Callback操作的线程关联性通过CallbackBehavior的UseSynchronizationContext 进行控制一样,service的线程关联性通过ServiceBehavir的UseSynchronizationContext 进行设定。UseSynchronizationContext 的默认值为true。

如何我们将CalculateService的UseSynchronizationContext 设为false:

   1: namespace Artech.ThreadAffinity.Services
   2: {
   3:     [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext = false)]
   4:     public class CalculateService:ICalculate
   5:     {
   6:         public static ListBox DisplayPanel
   7:         { get; set; }       
   8:  
   9:         #region ICalculate Members 
  10:  
  11:         public void Add(double op1, double op2)
  12:         {
  13:             double result = op1 + op2;
  14:             ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();            
  15:  
  16:            DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
  17:  
  18:             callback.DisplayResult(result);
  19:         } 
  20:  
  21:         #endregion
  22:     }
  23: } 
  24:  

有control被不是创建它的线程操作,肯定会抛出一个InvalidOperationException,就像这样:

image

我们一样可以通过SynchonizationContext或者ISynchronizeInvoke的方式来解决这样的问题,我们只讨论前面一种,为此我们改变了CalculateService的定义:通过SynchonizationContext的Post方法实现对ListBox的访问。

   1: namespace Artech.ThreadAffinity.Services
   2: {
   3:     [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext = false)]
   4:     public class CalculateService:ICalculate
   5:     {
   6:         public static ListBox DisplayPanel
   7:         { get; set; } 
   8:  
   9:         public static SynchronizationContext SynchronizationContext
  10:         { get; set; } 
  11:  
  12:         #region ICalculate Members 
  13:  
  14:         public void Add(double op1, double op2)
  15:         {
  16:             double result = op1 + op2;
  17:             ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
  18:            SynchronizationContext.Post(delegate
  19:             {
  20:                 DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
  21:             }, null); 
  22:  
  23:             callback.DisplayResult(result);            
  24:         } 
  25:  
  26:         #endregion
  27:     }
  28: } 
  29:  

通过static属性定义的SynchonizationContext在host的时候指定:

   1: private void HostForm_Load(object sender, EventArgs e)
   2: {    
   3:     this._serviceHost = new ServiceHost(typeof(CalculateService));
   4:     CalculateService.DisplayPanel = this.listBoxResult;
   5:    CalculateService.SynchronizationContext = SynchronizationContext.Current;
   6:     this._serviceHost.Opened += delegate
   7:     { 
   8: this.Text = "The calculate service has been started up!";
   9:     }; 
  10:  
  11:     this._serviceHost.Open();
  12: } 
  13:  

这样我们的程序又可以正常运行了。

WCF后续之旅:
WCF后续之旅(1): WCF是如何通过Binding进行通信的
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher
WCF后续之旅(4):WCF Extension Point 概览
WCF后续之旅(5): 通过WCF Extension实现Localization
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I]
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II]
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]
WCF后续之旅(14):TCP端口共享
WCF后续之旅(15): 逻辑地址和物理地址
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
WCF后续之旅(17):通过tcpTracer进行消息的路由

转载于:https://www.cnblogs.com/artech/archive/2008/08/21/1273021.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成年人改变生活的方式,都是从它开始

全世界只有3.14 % 的人关注了青少年数学之旅2019已经不足80天&#xff0c;年初立下的flag倒了几个&#xff1f;史蒂夫马丁有一句话&#xff1a;“所有的人生谜语都可以从阅读中找到答案。”无论是读影评、读好书&#xff0c;亦或者涉猎趣闻轶事、汲取犀利观点&#xff0c;总会悄…

表白和被表白时遇到的骚操作,最后一个天秀!| 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅【1】【2】【3】【4】【5】【6】【7】【8】【9】

这么简单的序列化 System.Text.Json.Serialization 也报错了?

咨询区 kofifus&#xff1a;我准备将项目中的 JSON.NET 切换到原生的 System.Text.Json&#xff0c;但我遇到了个意外报错&#xff0c;测试代码如下&#xff1a;using System.Text.Json.Serialization; using Newtonsoft.Json;public class C {public C(string PracticeName) {…

直观机械动图,一秒看懂原理!

全世界只有3.14 % 的人关注了青少年数学之旅这些神奇唯美的机械动图&#xff0c;让我们不得不感叹制造的魅力&#xff01;仿生四翼飞行机器人▲单手磁性拉链▲风洞实验——模拟飞机飞行▲可以说是最快的折弯机了▲这是谁设计的死循环▲高压水除锈▲摩擦焊接▲打地洞▲能轻松把车…

数据校验器架构模式组

刘 岳林 (yuelin_liumsn.com), 软件工程师2007 年 1 月 15 日本文阐述软件架构与设计模式&#xff0c;它为架构师和开发人员提供了一组关于数据校验的架构模式&#xff08;隔离校验器&#xff0c;可组装校验器&#xff0c;动态策略校验器&#xff0c;动态注册校验器等&#xff…

罗斯文2007(Northwind 2007)数据库、Access 2007 样列数据库分析[转]

Northwind 2007 是 MS 的一套销售类样列库&#xff0c;本片文章主要描述&#xff0c;它的结构及关系。 起因:   前些阵子一直想做一个虚拟项目来学习巩固一下 .net3.0 的一些新技术、为什么要做虚拟项目&#xff1f; 当然是系统的学习一些东西了、我对虚拟项目的要求是不能太…

WPF 实现任务栏角徽

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织欢迎转发、分享、点赞、在看&#xff0c;谢谢~。 01—效果预览效果预览&#xff08;更多效果请下载源码体验&#xff09;&#xff1a;一、TaskbarItemInfoExample.xaml代码如下 <Window x:Cla…

竞赛奇葩队名,学编程的人都是隐藏的段子手 | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅

史上最具争议的博弈游戏,我用概率论、博弈论找到了答案

全世界只有3.14 % 的人关注了青少年数学之旅要说最近人气最火热的游戏&#xff0c;莫过于前段时间刚崛起的“多多自走棋”。而关于“多多自走棋 ”被讨论得最多的就是&#xff0c;“到底是靠运气还是技术?”都说吃鸡靠运气&#xff0c;但这些道具加成&#xff0c;在数学计算面…

akka linux 端口,Actor模型开发库 Akka

Akka 是一个用 Scala 编写的库&#xff0c;用于简化编写容错的、高可伸缩性的 Java 和 Scala 的 Actor 模型应用。Actor模型并非什么新鲜事物&#xff0c;它由Carl Hewitt于上世纪70年代早期提出&#xff0c;目的是为了解决分布式编程中一系列的编程问题。其特点如下&#xff1…

简单粗暴的肢体语言解读攻略 | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅&#xff08;图源网络&#xff0c;侵权删&#xff09;

基于事件驱动架构构建微服务第1部分:应用程序特定的业务规则

原文链接&#xff1a;https://logcorner.com/building-microservices-through-event-driven-architecture-part1-application-specific-business-rules/如今&#xff0c;洋葱或六边形等架构为代码的可测试性和维护、与外部框架的独立性提供了重要帮助。在本教程中&#xff0c;我…

JavaWeb 项目启动时,后台开启一个线程的方法

原文链接&#xff1a;http://blog.csdn.net/it_wangxiangpan/article/details/7168286JavaWeb 服务启动时&#xff0c;在后台启动加载一个线程。目前&#xff0c;我所掌握的一共有两种方法&#xff0c;第一种是监听&#xff08;Listener&#xff09;&#xff0c;第二种是配置随…

linux脚本 逻辑运算,Linux-shell-逻辑运算和;

7.Shell7.5.2命令执行的判断依据&#xff1a; ; , &&, ||1.cmd ; cmd (不考虑指令相关性的连续指令下达)一般用于多条命令之间没有直接需求联系&#xff0c;最多只是有一个执行先后的关系。[rootlocalhost tmp]# sync; shutdown -h now2.与&&或 ||指令下达情况…

我居然从一只猫身上学到了斐波那契数列

猫的数学这么好是有原因的斐波那契数列&#xff08;Fibonacci sequence&#xff09;是由数学家列昂纳多斐波那契定义的把它写成数列的形式是这样的&#xff1a;1,1,2,3,5,8,13,21,34,55,89,...比如&#xff1a;人的耳朵比如&#xff1a;台风比如&#xff1a;松果的底部螺纹从两…

听说过Netflix的Chaos Monkey吗?不用羡慕,我们.NET也有

Chaos Monkey&#xff0c;是Netflix工程师创建的一种故障注入系统&#xff0c;它会随机在生产实例中引发各种各样的故障或异常&#xff0c;以确保它们的系统能够在这样的情况下存活&#xff0c;而不会对客户造成任何影响。可见&#xff0c;Chaos Monkey可以提高系统的安全和可用…

PHP中session与cookie的简单使用

2019独角兽企业重金招聘Python工程师标准>>> cookie简单实例&#xff1a; <?php if($_GET[out]){ //注销cookie setcookie(id,); setcookie(password,); echo "<script>location.hrefcookie.php</script>"; …

NetBeans Weekly News 刊号 # 27 - Sep 24, 2008

刊号 # 27 - Sep 24, 2008 日程表 注册 NetBeans Day--圣保罗&#xff0c;巴西&#xff08;十月一日&#xff09; 欢迎来到巴西圣保罗的 Sun Tech Days 。赶快在十月一日加入我们的 NetBeans Deep Dive 吧&#xff01;注册 NetBeans Day 是免费的&#xff0c;即使您不参加 Sun …

做项目开发你必须得掌握的知识:设计模式

先分享一个小故事 两个年轻人是大学同班同学&#xff0c;毕业后被同一家公司录取&#xff0c;可以说是站在相同的起跑线上。两人对未来也都是信心满满&#xff0c;踌躇满志。其中一人怀抱满腔激情&#xff0c;到处学习热门框架&#xff0c;但受限于公司体量和业务逻辑&#xff…

985硕博士:你为什么比我差?

全世界只有3.14 % 的人关注了青少年数学之旅身边总有些人看上去很轻松&#xff0c;不仅在工作中游刃有余&#xff0c;还知识渊博&#xff0c;对各种事情有自己的思考。这次&#xff0c;我们非常认真地筛选了这些公众号&#xff0c;他们专注于内容&#xff0c;关心当下发生的事情…