汐丫汐吖 發表於 2025-9-10 12:00:00

推荐一款线程or进程间数据同步解决方案

<p>在实际开发中,数据同步非常重要,特别是跨线程或者进程之间数据交互的时候,如果不能进行数据同步管理,那各个线程或者进程之间就会产生数据错乱问题,比如A程序用的是更新之前的数据,B数据用的是更新之后的数据,这会造成很严重的后果。今天我们以一个简单的小例子,介绍一款线程or进程之间数据同步的解决方案,谨供学习分享使用,如有不足之处,还请指正。</p>
<p>&nbsp;</p>
<h1>为什么要进行同步?</h1>
<p>&nbsp;</p>
<p>在多线程或者多进程编程中,有些资源属于临界资源,同一时刻只允许一个程序进行访问,我们把那些需要资源共享,相互等待,相互协作,又相互制约,各程序之间按照一定的顺序,步骤执行的过程称为同步,如果是多线程编程中,称之为线程同步;如果是不同进程中的同步,称之为进程同步。在C#编程中,可以通过EventWaitHandle进行线程或者进程间的同步。</p>
<p>&nbsp;</p>
<h1>什么是EventWaitHandle?</h1>
<p>&nbsp;</p>
<p>EventWaitHandle(事件等待句柄(亦简称为“事件”))允许线程通过信号和等待信号相互通信。它可以收到信号以释放一个或多个等待线程的等待句柄。 收到信号后,事件等待句柄便会进行手动或自动重置。EventWaitHandle 类可以表示本地事件等待句柄(本地事件)或命名系统事件等待句柄(命名事件或系统事件,对所有进程可见)。EventWaitHandle有两种模式:AutoReset和ManualReset。为了方便使用,还有两个对应的派生类,AutoResetEvent和ManualResetEvent。如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250907232529777-1061501352.png"></p>
<p>&nbsp;</p>
<h1>EventWaitHandle使用方法</h1>
<p>&nbsp;</p>
<p>可以通过EventWaitHandle的构造函数或者打开现有的命名事件的方式创建对象,通过构造函数它需要传入一个bool值,表示是否初始值为有信号,和EventResetMode,表示事件信号重置方式。</p>
<p>EventResetMode共有两个信号重置方式:</p>
<ul>
<li>AutoReset:表示在事件设置为有信号后,会自动设置为无信号,时间间隔为1000纳秒以内,适用于需要信号同步频率较高的场景,就像过刷卡过闸机一样,一次只允许一个人通过。</li>
<li>ManualReset:表示设置为有信号后,需要手动调用Reset方法设置为无信号,否则一直为有信号状态。它适用于需要信号同步频率适中的场景,比如电梯门开以后 ,同时允许多个人进入。</li>
</ul>
<p>&nbsp;</p>
<p>关于EventWaitHandle的工作原理,可以通过下图直观的描述:</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909230151602-997636219.png"></p>
<p>&nbsp;</p>
<h1>EventWaitHandle-AutoReset</h1>
<p>&nbsp;</p>
<p>采用AutoReset模式初始化EventWaitHandle对象,代码如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code> private EventWaitHandle autoResetEventWaitHandle;

private void InitAutoWaitHandleThread()
{
        if (autoTasks.Count &gt; 0 &amp;&amp; autoTasks.Any(item =&gt; item.Status == TaskStatus.Running || item.Status == TaskStatus.Created))
        {
                MessageBox.Show("Task is not completed");
                return;
        }
        autoTasks.Clear();
        this.txtMsg.Clear();
        if (autoResetEventWaitHandle == null)
        {
                autoResetEventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
        }

        int count = int.Parse(this.txtCount.Text);

        var task = Task.Run(() =&gt;
        {
                for (int i = 0; i &lt; count; i++)
                {
                        autoResetEventWaitHandle.WaitOne();
                        this.Invoke(() =&gt;
                        {
                                this.txtMsg.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] 第 {i} 个 is pass.{Environment.NewLine}");
                        });
                }
        });
        autoTasks.Add(task);
}</code></pre>
<p>&nbsp;</p>
<p>在上述示例中,我们创建了EventWaitHandle在实例对象,默认初始无信号(initialState:false)及EventResetMode为AutoReset,并在Task后台线程中调用它的WaitOne等待信号,如果接收到信号,则输出信息到文本框中,否则一直等待。我们通过按钮点击事件来设置信号,如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private void BtnReset03_Click(object? sender, EventArgs e)
{
        if (this.autoResetEventWaitHandle == null)
        {
                this.autoResetEventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
        }
        if (autoTasks.Count &lt; 1 || !autoTasks.Any(item =&gt; item.IsCompleted == false))
        {
                MessageBox.Show("当前没有线程在排队");
                return;
        }
        this.autoResetEventWaitHandle.Set();
}</code></pre>
<p>&nbsp;</p>
<p>在上述代码中,首先判断EventWaitHandle对象是否为空,如果对象为空则创建 ;然后判断当前Task后台线程是否在运行,并等待信号,如果没有则返回 。如果有正在运行的线程,则每点击一次,设置一次信号。由于EventResetMode为AutoReset,所以不需要手动调用Reset方法,EventWaitHandle对象会自动设置为无信号。EventWaitHandle-AutoReset实例演示如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909224942097-1686816670.gif"></p>
<p>&nbsp;</p>
<h1>EventWaitHandle-ManualReset</h1>
<p>&nbsp;</p>
<p>采用ManualReset模式创建EventWaitHandle对象,代码如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private EventWaitHandle manualResetEventWaitHandle;

private void InitManualWaitHandlerThread()
{
    if (manualTasks.Count &gt; 0 &amp;&amp; manualTasks.Any(item =&gt; item.Status == TaskStatus.Running || item.Status == TaskStatus.Created))
    {
      MessageBox.Show("Task is not completed");
      return;
    }
    manualTasks.Clear();
    this.txtMsg.Clear();
    if (manualResetEventWaitHandle == null)
    {
      manualResetEventWaitHandle = new EventWaitHandle(false,EventResetMode.ManualReset);//传入一个bool值,表示是否在初始化时有信号,true:有信号,false:无信号
    }

    int count = int.Parse(this.txtCount.Text);//此处做为排队人数

    var task = Task.Run(() =&gt;
    {
      for (int i = 0; i &lt; count; i++)
      {
            manualResetEventWaitHandle.WaitOne();
            Task.Delay(500).Wait(); // 每隔500ms,如果有信号就可以通过
            this.Invoke(() =&gt;
            {
                this.txtMsg.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] 第{i}个 is pass.{Environment.NewLine}");
            });
      }
    });
    manualTasks.Add(task);

}</code></pre>
<p>&nbsp;</p>
<p>在上述代码中,我们以ManualReset的方式初始化EventWaitHandle对象,在Task后台线程中调用WaitOne等待信号,当接收到信号后,在文本框中输出信息。我们同样通过手动点击按钮来设置信号,与AutoReset方式不同,ManualReset需要显式的调用Reset方法,将状态重置为无信号。在本示例中,我们点击事件1秒后,再调用Reset方法。如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private void BtnReset04_Click(object? sender, EventArgs e)
{
        if (this.manualResetEventWaitHandle == null)
        {
                this.manualResetEventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
        }
        if (manualTasks.Count &lt; 1 || !manualTasks.Any(item =&gt; item.IsCompleted == false))
        {
                MessageBox.Show("当前没有线程在排队");
                return;
        }
        //设置为有信号
        this.manualResetEventWaitHandle.Set();
        Task.Run(() =&gt;
        {
                //等待1s后,重置为无信号
                Task.Delay(1000).Wait();
                //设置为无信号
                this.manualResetEventWaitHandle.Reset();
        });
}</code></pre>
<p>&nbsp;</p>
<p>EventWaitHandle-ManualReset示例演示如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909225201288-527679483.gif"></p>
<p>&nbsp;</p>
<p>为了方便使用,EventWaitHandle还有两个派生类,分别为AutoResetEvent和ManualResetEvent,分别对应EventResetMode的两种类型,接下来分别介绍。</p>
<p>&nbsp;</p>
<h1>AutoResetEvent</h1>
<p>&nbsp;</p>
<p>AutoResetEvent是EventWaitHandle的派生类,对应EventWaitHandle的AutoReset类型,在设置信号后自动重置为无信号,示例如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private AutoResetEvent autoResetEvent;

/// &lt;summary&gt;
/// 初始化自动Reset线程
/// &lt;/summary&gt;
private void InitAutoResetThread()
{
        if (autoTasks.Count &gt; 0 &amp;&amp; autoTasks.Any(item =&gt; item.Status == TaskStatus.Running || item.Status == TaskStatus.Created))
        {
                MessageBox.Show("Task is not completed");
                return;
        }
        autoTasks.Clear();
        this.txtMsg.Clear();
        if (autoResetEvent == null)
        {
                autoResetEvent = new AutoResetEvent(false);//传入一个bool值,表示是否在初始化时有信号,true:有信号,false:无信号
        }

        int count = int.Parse(this.txtCount.Text);

        var task = Task.Run(() =&gt;
        {
                for (int i = 0; i &lt; count; i++)
                {
                        autoResetEvent.WaitOne();
                        this.Invoke(() =&gt;
                        {
                                this.txtMsg.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] 第 {i} 个 is pass.{Environment.NewLine}");
                        });
                }
        });
        autoTasks.Add(task);
}</code></pre>
<p>&nbsp;</p>
<p>在上述示例中,运行一个Task后台线程,在线程中每接收到一个信号,就输出一段文本到TextBox中,共10次。接收信号通过页面按钮点击的方式进行输出,如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private void BtnReset01_Click(object? sender, EventArgs e)
{
        if (this.autoResetEvent == null)
        {
                this.autoResetEvent = new AutoResetEvent(false);
        }
        if (autoTasks.Count &lt; 1 || !autoTasks.Any(item =&gt; item.IsCompleted == false))
        {
                MessageBox.Show("当前没有线程在排队");
                return;
        }
        this.autoResetEvent.Set();
}</code></pre>
<p>&nbsp;</p>
<p>AutoResetEvent实例演示如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909225320639-1540367211.gif"></p>
<p>&nbsp;</p>
<h1>ManualResetEvent</h1>
<p>&nbsp;</p>
<p>ManualResetEvent是EventWaitHandle的派生类,对应EventWaitHandle的ManualReset类型,在设置信号后需要手动重置为无信号,示例如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private ManualResetEvent manualResetEvent;

/// &lt;summary&gt;
/// 初始化手动Reset线程
/// &lt;/summary&gt;
private void InitManualResetThread()
{
        if (manualTasks.Count &gt; 0 &amp;&amp; manualTasks.Any(item =&gt; item.Status == TaskStatus.Running || item.Status == TaskStatus.Created))
        {
                MessageBox.Show("Task is not completed");
                return;
        }
        manualTasks.Clear();
        this.txtMsg.Clear();
        if (manualResetEvent == null)
        {
                manualResetEvent = new ManualResetEvent(false);//传入一个bool值,表示是否在初始化时有信号,true:有信号,false:无信号
        }

        int count = int.Parse(this.txtCount.Text);//此处做为排队人数

        var task = Task.Run(() =&gt;
        {
                for (int i = 0; i &lt; count; i++)
                {
                        manualResetEvent.WaitOne();
                        Task.Delay(500).Wait(); // 每隔500ms,如果有信号就可以通过
                        this.Invoke(() =&gt;
                        {
                                this.txtMsg.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] 第{i}个 is pass.{Environment.NewLine}");
                        });
                }
        });
        manualTasks.Add(task);

}</code></pre>
<p>&nbsp;</p>
<p>在上述示例中,运行一个Task后台线程,在线程中接收到信号时,间隔500ms,就输出一段文本到TextBox中,如果没有事件等待句柄没有重置为无信号,则一直输出,共10次。接收信号通过页面按钮点击的方式进行输出,如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private void BtnReset02_Click(object? sender, EventArgs e)
{
        if (this.manualResetEvent == null)
        {
                this.manualResetEvent = new ManualResetEvent(false);
        }
        if (manualTasks.Count &lt; 1 || !manualTasks.Any(item =&gt; item.IsCompleted == false))
        {
                MessageBox.Show("当前没有线程在排队");
                return;
        }
        //设置为有信号
        this.manualResetEvent.Set();
        Task.Run(() =&gt;
        {
                //等待1s后,重置为无信号
                Task.Delay(1000).Wait();
                //设置为无信号
                this.manualResetEvent.Reset();
        });
}</code></pre>
<p>&nbsp;</p>
<p>ManualResetEvent实例演示如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909225443255-1523386959.gif"></p>
<p>&nbsp;</p>
<p>在上述介绍的示例中,EventWaitHandle和它的两个派生类AutoResetEvent,及ManualResetEvent都是在线程间实现同步,而如果要在进程间实现同步,则需命名事件才能实AutoResetEvent,及ManualResetEvent并没有实现命名事件的构造函数,所以不支持进程间同步,要实现进程间同步,则 只能通过EventWaitHandle来实现。</p>
<p>&nbsp;</p>
<h1>命名事件</h1>
<p>&nbsp;</p>
<p>Windows操作系统允许命名事件等待句柄,命名事件是在整个操作系统范围内生效,当创建命名事件后,它在所有进程间可见。因此,命名事件可用于同步进程的活动以及线程。由于命名事件是系统范围的,因此可以有多个&nbsp;EventWaitHandle&nbsp;对象表示同一命名事件。 每次调用构造函数或&nbsp;OpenExisting&nbsp;方法时,都会创建一个新&nbsp;EventWaitHandle&nbsp;对象。 重复指定同一名称会创建表示同一命名事件的多个对象。不过为了安全谨慎起见“建议谨慎使用命名事件”。</p>
<p>本示例创建两个可执行程序,分别运行来表示两个进程,如下所示:</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909224543306-2123656471.png"></p>
<p>&nbsp;</p>
<p>在控制台程序“Okcoder.Sync.DemoConsole”中,创建一个命名事件等待句柄,如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>namespace Okcoder.Sync.DemoConsole
{
    internal class Program
    {
      static void Main(string[] args)
      {
            Console.WriteLine("-------------------okcoder begin---------------------");
            string eventWaitHandleName = "okcoder";
            EventWaitHandle? eventWaitHandle = null;
            if (!EventWaitHandle.TryOpenExisting(eventWaitHandleName, out eventWaitHandle))
            {
                eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventWaitHandleName);
            }
            
            for (int i = 0; i &lt; 10; i++)
            {
                eventWaitHandle.WaitOne();
                Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] 第 {i} 个 is pass.");
            }
            eventWaitHandle.Close();
            eventWaitHandle.Dispose();
            Console.WriteLine("-------------------okcoder end-----------------------");
      }
    }
}</code></pre>
<p>&nbsp;</p>
<p>在上述示例中,通过EventWaitHandle的构造函数,创建个EventWaitHandle对象,默认无信号,并采用AutoReset模式,唯一不同的是,给此对象创建了一个名称。</p>
<p>在另一个程序“Okcoder.Sync.Demo”的FrmMain页面,通过按钮设置信号,如下所示:</p>
<p>&nbsp;</p>
<pre class="language-csharp highlighter-hljs"><code>private void BtnReset05_Click(object? sender, EventArgs e)
{
    string eventWaitHandleName = "okcoder";
    if (EventWaitHandle.TryOpenExisting(eventWaitHandleName,out EventWaitHandle? result))
    {
      result.Set();
    }
    else
    {
      MessageBox.Show($"打开名称为 {eventWaitHandleName} 的Event Wait Handle 失败.");
    }
}</code></pre>
<p>&nbsp;</p>
<p>上述示例中,通过EventWaitHandle的TryOpenExisting方法,获取统一名称的事件等待句柄,如果获取不到,则报错。如果获取到,则调用Set方法设置信号。</p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1068941/202509/1068941-20250909224340669-1994890700.gif"></p>
<p>&nbsp;</p>
<p>以上就是《推荐一款线程or进程间数据同步解决方案》的全部内容,旨在抛砖引玉,一起学习,共同进步。</p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <div id="AllanboltSignature">

    <p style="border-top: #e0e0e0 1px dashed; border-right: #e0e0e0 1px dashed; border-bottom: #e0e0e0 1px dashed; border-left: #e0e0e0 1px dashed; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 30px; font-family: 微软雅黑; font-size: 12px" id="PSignature">
<br>

   <img alt="" src="https://images.cnblogs.com/cnblogs_com/hsiang/1154298/o_115f1cd8.jpg" width="80px" height="80px">
   
    作者:老码识途
    <br>
    出处:http://www.cnblogs.com/hsiang/
    <br>
    本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
    <br>关注个人公众号,定时同步更新技术及职场文章
<br><br>
   </p>
</div><br><br>
来源:https://www.cnblogs.com/hsiang/p/19078924
頁: [1]
查看完整版本: 推荐一款线程or进程间数据同步解决方案