• 多线程新手篇


    由于winform的缘故,最近一周都在研究多线程,以前也看过多线程方面的资料,一个WaitOne就把我难倒了。呵呵,现在总算搞清楚了。

    本片(参考自codeproject)主要讲以下内容:
    (一).Sleep和Join的用法
    (二).Invoke和BeginInvoke的使用及区别
    (三).Monitor类的使用
    (四).AutoResetEvent使用

    (一)1.Sleep()方法,调用后,线程会被阻止指定的时间,时间到了后,会继续执行。
    线程需引用using System.Threading;命名空间。

    Code

    2.Join()方法,肯定有两个线程,Join才有用。
    比如两个线程T1,T2
    你在T2里写上T1.Join()的话,在执行的时候,T2会被Block,直到T1是的状态是stopped。

    Code

    (二).Invoke和BeginInvoke

    Code

    由于程序的并行执行,所以会交替输出。

    Code

    这个输出和上面输出差不多,也是交替输出的。

    Code

    BeginInvoke()的返回值是IAsyncResult类型的,表示dddddddddddddddd
    EndInvoke()可以获得返回值。

    Code

    Winform中的多线程与UI

    Code

    大家看到上面这段代码没任何问题,就是往窗体上面添加个TextBox控件。

    Code

    大家再看这段代码,貌似也没有问题,但是运行的时候,就会抛出异常。
    线程间操作无效: 从不是创建控件“Form2”的线程访问它。
    出错的原因在于,主线程创建了form窗体,别的线程是不能操作它的。

    Code

    此处首先声明了委托MyDelegate。
     this.Invoke(delInstance); 中的this代表的是form对象,实际上还是主线程执行的AddControl
    Invoke方法,会阻塞别的线程,直到自己的调用返回了,才继续往下执行,就是说Invoke()调用后会立即返回。
    BeginInvoke就不会阻塞别的线程。这是二者的区别之一。

    Code

    上面的代码中,我们没必要用Invoke方法,因为我们是在主线程中,主线程中可以直接更新UI的。但是在一些情况下,我们不知道是否个别方法是在主线程里还是别的线程里执行的,并且,调用者不知道是否个别方法有更新UI的代码。所以,如果更新UI的话,应该切换到主线程里去执行。
    其实没那么麻烦,InvokeRequired属性就是为了这个目的,如果当前线程不是主线程,InvokeRequired就返回true,是主线程就返回false。因此,任何更新UI的代码应该先判断InvokeRequired。

    Code

    (三).Monitor.Wait()方法,msdn解释:当前拥有指定对象上的锁的线程调用此方法以释放该对象,以便其他线程可以访问它。调用方在等待重新获取锁期间被阻止。当调用方需要等待另一个线程操作导致的状态更改时,将调用此方法。

    当线程调用 Wait 时,它释放对象的锁并进入对象的等待队列。对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用。所有调用 Wait 的线程都将留在等待队列中,直到它们接收到由锁的所有者发送的 Pulse 或 PulseAll 的信号为止。如果发送了 Pulse,则只影响位于等待队列最前面的线程。如果发送了 PulseAll,则将影响正等待该对象的所有线程。接收到信号后,一个或多个线程将离开等待队列而进入就绪队列。就绪队列中的线程被允许重新获取锁。

    当调用线程重新获取对象上的锁后,此方法将返回。请注意,如果锁的持有者不调用 Pulse 或 PulseAll,则此方法将无限期地阻止。

    调用方执行一次 Wait,与已为指定对象调用 Enter 的次数无关。概念上,Wait 方法存储调用方对对象调用 Enter 的次数,并按完全释放锁定对象所需要的次数调用 Exit。然后调用方在等待重新获取对象期间被阻止。当调用方重新获取锁时,系统按还原调用方的已保存 Enter 计数所需要的次数调用 Enter。调用 Wait 仅释放指定对象的锁;如果调用方是其他对象的锁的所有者,则不释放这些锁。

    (四)

    Code
    Code

     Wait示例

    Wait
    private static object lockObject = new object();

    static void Main(string[] args)
    {
    Console.WriteLine(
    "主线程运行,线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
    Thread t
    = new Thread(DoWork);
    t.Start();
    Monitor.Enter(lockObject);
    Console.WriteLine(
    "主线程得到锁,开始运行");

    Console.WriteLine(
    "主线程释放锁,开始等待");
    Monitor.Wait(lockObject);
    Console.WriteLine(
    "主线程重新得到锁,结束等待");//这句得等到worker线程pulse之后才能执行


    Console.Read();
    }

    static void DoWork()
    {
    Console.WriteLine(
    "worker线程运行,线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
    Console.WriteLine(
    "停顿10秒");
    int i = 1;
    while (i <= 10)
    {
    Console.WriteLine(i.ToString());
    Thread.Sleep(
    1000);
    i
    ++;
    if (i == 5)
    {
    Monitor.Enter(lockObject);
    Monitor.Pulse(lockObject);
    Monitor.Exit(lockObject);
    }
    }
    }

     买书示例:

    代码
    下面我们来举一个例子:我去书店买书,当我选中一本书后我会去收费处付钱,
    付好钱后再去仓库取书。这个顺序不能颠倒,我作为主线程,收费处和仓库做两个辅助线程,代码如下:

    using System;
    using System.Linq;
    using System.Activities;
    using System.Activities.Statements;
    using System.Threading;

    namespace CaryAREDemo
    {
    class Me
    {
    const int numIterations = 550;
    static AutoResetEvent myResetEvent = new AutoResetEvent(false);
    static AutoResetEvent ChangeEvent = new AutoResetEvent(false);
    //static ManualResetEvent myResetEvent = new ManualResetEvent(false);
    //static ManualResetEvent ChangeEvent = new ManualResetEvent(false);
    static int number; //这是关键资源

    static void Main()
    {
    Thread payMoneyThread
    = new Thread(new ThreadStart(PayMoneyProc));
    payMoneyThread.Name
    = "付钱线程";
    Thread getBookThread
    = new Thread(new ThreadStart(GetBookProc));
    getBookThread.Name
    = "取书线程";
    payMoneyThread.Start();
    getBookThread.Start();

    for (int i = 1; i <= numIterations; i++)
    {
    Console.WriteLine(
    "买书线程:数量{0}", i);
    number
    = i;
    //Signal that a value has been written.
    myResetEvent.Set();
    ChangeEvent.Set();
    Thread.Sleep(
    0);
    }
    payMoneyThread.Abort();
    getBookThread.Abort();
    }

    static void PayMoneyProc()
    {
    while (true)
    {
    myResetEvent.WaitOne();
    //myResetEvent.Reset();
    Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
    }
    }
    static void GetBookProc()
    {
    while (true)
    {
    ChangeEvent.WaitOne();
    // ChangeEvent.Reset();
    Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
    Console.WriteLine(
    "------------------------------------------");
    Thread.Sleep(
    0);
    }
    }
    }
    }
    运行结果如下:
    如果本文的描述的方法或内容有问题,请给我留言。
  • 相关阅读:
    MyBatis的缓存
    16年随笔
    linux 随笔
    Linux下启动Tomcat启动并显示控制台日志信息
    linux 连接工具
    Linux Tomcat重新启动
    SpringMVC 文件上传 MultipartFile
    spring @component
    mysql转型
    MyBatis传入参数
  • 原文地址:https://www.cnblogs.com/lhking/p/1531513.html
Copyright © 2020-2023  润新知