谈谈C#多线程开发:并行、并发与异步编程
<h3>阅读导航</h3><p>一、使用Task</p>
<p>二、并行编程</p>
<p>三、线程同步</p>
<p>四、异步编程模型</p>
<p>五、多线程数据安全</p>
<p>六、异常处理</p>
<p> </p>
<h3>概述</h3>
<p>现代程序开发过程中不可避免会使用到多线程相关的技术,之所以要使用多线程,主要原因或目的大致有以下几个:</p>
<p>1、 业务特性决定程序就是多任务的,比如,一边采集数据、一边分析数据、同时还要实时显示数据;</p>
<p>2、 在执行一个较长时间的任务时,不能阻塞UI界面响应,必须通过后台线程处理;</p>
<p>3、 在执行批量计算密集型任务时,采用多线程技术可以提高运行效率。</p>
<p>传统使用的多线程技术有:</p>
<ol>
<li>Thread & ThreadPool</li>
<li>Timer</li>
<li>BackgroundWorker</li>
</ol>
<p align="left">目前,这些技术都不再推荐使用了,目前推荐采用基于任务的异步编程模型,包括并行编程和Task的使用。</p>
<p align="left"> </p>
<h3>一、使用Task:</h3>
<p>大部分情况下,多线程的应用场景是在后台执行一个较长时间的任务时,不能阻塞界面响应,同时,任务还是可以取消的。</p>
<p>下面我们实现一个简单的示例功能:用户点击Start按钮时启动一个任务,任务执行过程中通过进度条显示任务进度,点击Stop按钮结束任务。</p>
<div class="cnblogs_code"><img id="code_img_closed_d7089f6a-b0af-41c9-8846-607cfe546396" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_d7089f6a-b0af-41c9-8846-607cfe546396" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_d7089f6a-b0af-41c9-8846-607cfe546396" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Form1 : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">volatile</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> CancelWork = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Form1()
{
InitializeComponent();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStart_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStop.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
CancelWork </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> WorkThread());
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStop_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
CancelWork </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> WorkThread()
{
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.progressBar.Value =<span style="color: rgba(0, 0, 0, 1)"> i;
}));
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(CancelWork)
{
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStop.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}));
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> 这个代码写的中规中矩,没什么特别的地方,仅仅是用Tsak取代了早期经常采用的Thread、ThreadPool等,虽然Task内部也是对ThreadPool的封装,但仍然建议尽量采用TASK来实现多任务。</p>
<p> 注意:虽然可以通过代码强行结束一个任务,但强烈建议不要这样做,应该给它一个通知让其自己结束。</p>
<p> </p>
<h3 align="left"><strong>二、</strong>并行编程:</h3>
<p>目标:通过一个计算素数的方法,循环计算并打印出10000以内的素数。</p>
<p>计算一个数是否素数的方法:</p>
<div class="cnblogs_code"><img id="code_img_closed_b6a40899-2f14-465c-9472-d954db675836" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_b6a40899-2f14-465c-9472-d954db675836" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_b6a40899-2f14-465c-9472-d954db675836" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsPrimeNumber(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number < <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number == <span style="color: rgba(128, 0, 128, 1)">1</span> && number == <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">2</span>; i < number; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number % i == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>如果不采用并行编程,常规实现方法:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span>; i <= <span style="color: rgba(128, 0, 128, 1)">10000</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">bool</span> b =<span style="color: rgba(0, 0, 0, 1)"> IsPrimeNumber(i);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{i}:{b}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>采用并行编程方法:</p>
<div class="cnblogs_code">
<pre> Parallel.For(<span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">10000</span>, x=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">bool</span> b =<span style="color: rgba(0, 0, 0, 1)"> IsPrimeNumber(x);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{i}:{b}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});</span></pre>
</div>
<p>运行程序发现时间差异并不大,主要原因是瓶颈在打印控制台上面,去掉打印代码,只保留计算代码,就可以看出性能差异。</p>
<p>Parallel实际是通过线程池进行任务的分配,线程池的最小线程数和最大线程数将影响到整个程序的性能,需要合理设置。(最小线程默认为8。)</p>
<div class="cnblogs_code">
<pre> ThreadPool.SetMinThreads(<span style="color: rgba(128, 0, 128, 1)">10</span>, <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">);
ThreadPool.SetMaxThreads(</span><span style="color: rgba(128, 0, 128, 1)">20</span>, <span style="color: rgba(128, 0, 128, 1)">20</span>);</pre>
</div>
<p> 按照上述设置,假设线程任务耗时比较长不能很快结束。在启动前面10个线程时速度很快,第10~20个线程就比较慢一点,大约0.5秒,到达20个线程以后,如果前期任务没有结束就不能继续分配任务了。</p>
<p>和Task类似,Parallel类仍然是对ThreadPool的封装,但Parallel有一个优势,它能知道所有任务是否完成,如果采用线程池来实现批量任务,我们需要自己通过计数的方式确定所有子任务是否全部完成。</p>
<p> Parallel类还有一个ForEach方法,使用和For类似,就不重复描述了。</p>
<p> </p>
<p></p>
<h3>三、 线程(或任务)同步</h3>
<p>有时我们需要通知一个任务结束,或一个任务等待某个条件进入下一个状态,这就需要用到任务同步的技术。</p>
<p>一个比较简单的方法就是定义一个变量来表示状态。</p>
<p align="left"> private volatile bool CancelWork = false;</p>
<p>后台任务可以轮询该变量进行判断:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(CancelWork)
{
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p> 这是我们常用的方法,可以称为线程状态机同步(虽然只有两个状态)。需要注意的是在通过轮询去读取状态时,循环体内至少应该有1ms的Sleep,不然CPU会很高。</p>
<p>线程同步还有一个比较好的办法就是采用ManualResetEvent 和AutoResetEvent :</p>
<div class="cnblogs_code"><img id="code_img_closed_783b5fea-dff0-4f2b-8db8-084b9917b442" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_783b5fea-dff0-4f2b-8db8-084b9917b442" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_783b5fea-dff0-4f2b-8db8-084b9917b442" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Form1 : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> ManualResetEvent manualResetEvent = <span style="color: rgba(0, 0, 255, 1)">new</span> ManualResetEvent(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Form1()
{
InitializeComponent();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStart_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStop.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
manualResetEvent.Reset();
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> WorkThread());
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStop_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
manualResetEvent.Set();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> WorkThread()
{
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.progressBar.Value =<span style="color: rgba(0, 0, 0, 1)"> i;
}));
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(manualResetEvent.WaitOne(<span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">))
{
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStop.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}));
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>采用WaitOne来等待比通过Sleep进行延时要更好,因为当执行manualResetEvent.WaitOne(1000)时,如果manualResetEvent没有调用Set,该方法在等待1000ms后返回false,如果期间调用了manualResetEvent的Set方法,该方法会立即返回true,不用等待剩下的时间。</p>
<p>采用这种同步方式优于采用通过内部字段变量进行同步的方式,另外尽量采用ManualResetEvent 而不是AutoResetEvent 。</p>
<p> </p>
<h3>四、异步编程模型(await、async)</h3>
<p>假设我们要实现一个简单的功能:当点击启动按钮时,运行一个任务,任务结束时要报告是否成功,如果成功就显示绿色图标、如果失败就显示红色图标,1秒后图标颜色恢复为白色;任务运行期间启动按钮要不可用。</p>
<p> 我写了相关代码:</p>
<div class="cnblogs_code"><img id="code_img_closed_c64885f5-35e6-40b2-824e-d7c949fab4a2" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_c64885f5-35e6-40b2-824e-d7c949fab4a2" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_c64885f5-35e6-40b2-824e-d7c949fab4a2" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Form1 : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStart_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(DoSomething())
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Green;
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Red;
}
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.White;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> DoSomething()
{
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">5000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> 这段代码逻辑清晰、条理清楚,一看就能明白,但存在两个问题:</p>
<p>1、运行期间UI线程阻塞了,用户界面没有响应;</p>
<p>2、根本不能实现需求,点击启动后,程序卡死6秒种,也没有看到颜色变化,因为UI线程已经阻塞,当重新获得句柄时图标已经是白色了。</p>
<p>为了实现需求,我们改用多任务来实现相关功能:</p>
<div class="cnblogs_code"><img id="code_img_closed_9fcfa3a9-363a-4028-b702-3f1aa12819db" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_9fcfa3a9-363a-4028-b702-3f1aa12819db" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_9fcfa3a9-363a-4028-b702-3f1aa12819db" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Form1 : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Form1()
{
InitializeComponent();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStart_Click(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (DoSomething())
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Green;
}));
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Red;
}));
}
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Invoke(<span style="color: rgba(0, 0, 255, 1)">new</span> Action(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.White;
}));
});
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> DoSomething()
{
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">5000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> 以上代码完全实现了最初的需求,但有几个不完美的地方:</p>
<p>1、主线程的btnStart_Click方法除了启动一个任务以外,啥事也没干;</p>
<p>2、由于非UI线程不能访问UI控件,代码里有很多Invoke,比较丑陋;</p>
<p>3、界面逻辑和业务逻辑掺和在一起,使得代码难以理解。</p>
<p>采用C#的异步编程模型,通过使用await、async关键字,可以更好地实现上述需求。 </p>
<div class="cnblogs_code"><img id="code_img_closed_584ce641-0dec-4109-ac9c-baddcf0f40ae" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_584ce641-0dec-4109-ac9c-baddcf0f40ae" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_584ce641-0dec-4109-ac9c-baddcf0f40ae" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Form1 : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Form1()
{
InitializeComponent();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> <span style="color: rgba(0, 0, 255, 1)">void</span> btnStart_ClickAsync(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, EventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> DoSomethingAsync();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(result)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Green;
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.Red;
}
</span><span style="color: rgba(0, 0, 255, 1)">await</span> Task.Delay(<span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.picShow.BackColor =<span style="color: rgba(0, 0, 0, 1)"> Color.White;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.btnStart.Enabled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> DoSomethingAsync()
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span> Task.Run(() =><span style="color: rgba(0, 0, 0, 1)">
{
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">5000</span><span style="color: rgba(0, 0, 0, 1)">);
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>这段代码看起来就像是同步代码,其业务逻辑是如此的清晰优雅,让人一目了然,关键是它还不阻塞线程,UI正常响应。</p>
<p>可以看到,通过使用await关键字,我们可以专注于业务功能实现,特别是后续任务需要前序任务的返回值的情况下,可以大量减少任务之间的同步操作,代码的可读性也大大增强。</p>
<p> </p>
<h3>五、 多线程环境下的数据安全</h3>
<p>目标:我们要向一个字典加入一些数据项,为了增加效率,我们使用了多个线程。</p>
<div class="cnblogs_code"><img id="code_img_closed_aa5ba943-ae77-4a28-95d4-935dcca834f8" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_aa5ba943-ae77-4a28-95d4-935dcca834f8" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_aa5ba943-ae77-4a28-95d4-935dcca834f8" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Test1()
{
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> AddData()
{
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 0, 1)">Dic.ContainsKey(i))
{
Dic.Add(i, i.ToString());
}
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>向字典重复加入同样的关键字会引发异常,所以在增加数据前我们检查一下是否已经包含该关键字。以上代码看似没有问题,但有时还是会引发异常:“已添加了具有相同键的项。”原因在于我们在检查是否包含该Key时是不包含的,但在新增时其他线程加入了同样的KEY,当前线程再增加就报错了。</p>
<p>【注意:也许你多次运行上述程序都能顺利执行,不报异常,但还是要清楚认识到上述代码是有问题的!毕竟,程序在大部分情况下都运行正常,偶尔报一次故障才是最头疼的事情。】</p>
<p>上述问题传统的解决方案就是增加锁机制。对于核心的修改代码通过锁来确保不会重入。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">object</span> locker4Add=<span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> AddData()
{
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)"> (locker4Add)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">Dic.ContainsKey(i))
{
Dic.Add(i, i.ToString());
}
}
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p>以上代码可以解决问题,但不是最佳方案。更好的方案是使用线程安全的容器:ConcurrentDictionary。</p>
<div class="cnblogs_code"><img id="code_img_closed_f1e1cf42-7668-448c-b7a4-61c0f28e56c6" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_f1e1cf42-7668-448c-b7a4-61c0f28e56c6" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_f1e1cf42-7668-448c-b7a4-61c0f28e56c6" class="cnblogs_code_hide">
<pre> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>> Dic = <span style="color: rgba(0, 0, 255, 1)">new</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Test1()
{
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)"> AddData());
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> AddData()
{
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i < <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
{
Dic.TryAdd(i, i.ToString());
Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> 你可以在新增前继续检查一下容器是否已经包含该Key,你也可以不用检查,TryAdd方法确保不会重复添加且不会产生异常。</p>
<p> 刚才是多个线程同时写某个对象,如果就单个线程写对象,其他多个线程仅仅是消费(访问)对象,是否可以使用非线程安全的容器呢?</p>
<p> 基本上来说多个线程读取一个对象是没有太大问题的,但还是会存在一些要注意的地方:</p>
<p>1、对于常用的List,在对其进行foreach时List对象不能被修改,不仅不能Remove,Add也不可以;否则会报一个异常:异常信息:”集合已修改;可能无法执行枚举操作。”</p>
<p>2、还有一个类似的问题 就是调用Dictionary的ToList方法时<strong>有时</strong>会报错,将Dictionary 类型改成ConcurrentDictionary类型,问题依然存在,其原因是ToList会读取字典的Count,创建相关大小的区域后执行复制,而此时字典的长度增加了。</p>
<p>以上只是描述了多线程数据访问的两个小例子,实际使用中相关的问题一定会远远不止这些,多线程程序的大部分异常都是因为资源竞争引起的(包括死锁),一定要小心处理。</p>
<p> </p>
<p></p>
<h3>六、多线程的异常处理</h3>
<h4>(一) 异常处理的几个基本原则</h4>
<p>1、 基本原则:<strong>不要轻易捕获根异常</strong>;</p>
<p>2、 组件或控件抛出异常时可以根据需要自定义一些异常,不要抛出根异常,可以直接使用的常用异常有:FormatException、IndexOutOfRangException、InvalidOperationException、InvalidEnumArgumentException ;没有合适的就自定义;</p>
<p>3、 用户自定义异常从ApplicationException继承;</p>
<p>4、 多线程的内部异常不会传播到主线程,应该在内部进行处理,可以通过事件推到主线程来;</p>
<p>5、应用程序层面可以捕获根异常,做一些记录工作,切不可隐匿异常。 </p>
<h4>(二) 异常处理方案(基于WPF实现)</h4>
<p><strong>主线程的异常处理:</strong></p>
<p>捕获你知道的异常,并自行处理,但不要轻易捕获根异常,下面的代码令人深恶痛绝:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
DoSomething();
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(Exception)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Do Nothing</span>
}</pre>
</div>
<p> 当然,如果你确定有能力捕获根异常,并且是业务逻辑的一部分,可以捕获根异常 :</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
DoSomething();
MessageBox.Show(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">OK</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(Exception ex)
{
MessageBox.Show($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ERROR:{ex.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p> </p>
<p><strong>可等待异步任务的异常处理:</strong></p>
<p>可等待的任务内的异常是可以传递到调用者线程的,可以按照主线程异常统一处理:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> DoSomething();
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(FormatException ex)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Do Something</span>
}</pre>
</div>
<p> </p>
<p><strong>Task任务内部异常处理:</strong></p>
<p>非可等待的Task任务内部异常是无法传递到调用者线程的,参考下面代码:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
Task.Run(() </span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span> s = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">aaa</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">.Parse(s);
});
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (FormatException ex)
{
MessageBox.Show(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p> 上面代码不会实现你期望的效果,它只会造成程序的崩溃。(有时候不会立即崩溃,后面会有解释)</p>
<p>处理办法有两个:</p>
<p>1、自行处理:(1)处理可以预料的异常,(2)同时处理根异常(写日志等),也可以不处理根异常,后面统一处理;</p>
<p>2、或将异常包装成事件推送到主线程,交给主线程处理。</p>
<div class="cnblogs_code"><img id="code_img_closed_6bbeac37-dd6a-4b4a-8b43-963c920fe3de" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_6bbeac37-dd6a-4b4a-8b43-963c920fe3de" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_6bbeac37-dd6a-4b4a-8b43-963c920fe3de" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FormSync : Form
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">event</span> EventHandler<UnhangdledExceptionArgs><span style="color: rgba(0, 0, 0, 1)"> UnhandledExceptionCatched;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Form_Load()
{
UnhandledExceptionCatched </span>+=<span style="color: rgba(0, 0, 0, 1)"> MainWindow_UnhandledExceptionCatched;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> MainWindow_UnhandledExceptionCatched(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, UnhangdledExceptionArgs e)
{
MessageBox.Show($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">CatchException:{e.InnerException.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Thread1()
{
Task.Run(()</span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ApplicationException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Thread Exception</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
UnhangdledExceptionArgs args </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UnhangdledExceptionArgs()
{
InnerException </span>=<span style="color: rgba(0, 0, 0, 1)"> ex
};
UnhandledExceptionCatched</span>?.Invoke(<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, args);
}
});
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UnhangdledExceptionArgs : EventArgs
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Exception InnerException { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p> </p>
<p><strong>Thread和ThreadPool内部异常:</strong></p>
<p>虽然不推荐使用Thread,如果实在要用,其处理原则和上述普通Task任务内部异常处理方案一致。</p>
<p> </p>
<p><strong>全局未处理异常的处理:</strong></p>
<p>虽然我们不推荐catch根异常,但如果一旦发生未知异常程序就崩溃,客户恐怕难以接受吧,如果要求所有业务模块都处理根异常并进行保存日志、弹出消息等操作又非常繁琐,所以,处理的思路是业务模块不处理根异常,但应用程序要对未处理异常进行统一处理。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> App : Application
{
App()
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.Startup +=<span style="color: rgba(0, 0, 0, 1)"> App_Startup;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> App_Startup(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, StartupEventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.DispatcherUnhandledException +=<span style="color: rgba(0, 0, 0, 1)"> App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException </span>+=<span style="color: rgba(0, 0, 0, 1)"> CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException </span>+=<span style="color: rgba(0, 0, 0, 1)"> TaskScheduler_UnobservedTaskException;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">主线程未处理异常</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> App_DispatcherUnhandledException(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
DoSomething(e.Exception);
e.Handled </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">未处理线程异常(如果主线程未处理异常已经处理,该异常不会触发)</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> CurrentDomain_UnhandledException(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, UnhandledExceptionEventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (e.ExceptionObject <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> Exception ex)
{
DoSomething(ex);
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">未处理的Task内异常</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> TaskScheduler_UnobservedTaskException(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, UnobservedTaskExceptionEventArgs e)
{
DoSomething(e.Exception);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">保存、显示异常信息</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ProcessException(Exception exception)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">保存日志
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">提醒用户</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<p>解释一下:</p>
<p>1、 当主线程发生未处理异常时会触发App_DispatcherUnhandledException事件,在该事件中如果设置e.Handled = true,那么系统不会崩溃,如果没有设置e.Handled = true,会继续触发CurrentDomain_UnhandledException事件(毕竟主线程也是线程),而CurrentDomain_UnhandledException事件和TaskScheduler_UnobservedTaskException事件触发后,操作系统都会强行关闭这个应用程序。所以我们应该在App_DispatcherUnhandledException事件中设置e.Handled = true。</p>
<p>2、Thread线程异常会触发CurrentDomain_UnhandledException事件,导致系统崩溃,所以建议尽量不要使用Thread和ThreadPool。</p>
<p>3、非可等待的Task内部异常会触发TaskScheduler_UnobservedTaskException事件,导致系统崩溃,所以建议Task内部自行处理根异常或将异常封装为事件推到主线程。需要额外注意一点:Task内的未处理异常不会被立即触发事件,而是要延迟到GC执行回收的时候才触发,这使得问题更复杂,需要小心处理。</p>
<p> </p>
<h3><strong>总之</strong></h3>
<p>当前,异步编程模型已经是.NET框架的基本功能了,特别是WEB开发,后台代码已经全面异步化了,所以每个C#开发人员都不能轻视它,必须熟练掌握。 虽然在一知半解的情况下也能写多线程程序,写的程序也能跑,但就是那些平时一切正常偶尔抽风一下的错误会让头痛不已。只有深刻了解多线程的内部原理,并遵循结构化的设计原则才能写出健壮、优美的代码。</p>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
<hr>
签名区:<br>
如果您觉得这篇博客对您有帮助或启发,请点击右侧【推荐】支持,谢谢!
<hr><br><br>
来源:https://www.cnblogs.com/seabluescn/p/12973936.html
頁:
[1]