solitaryclown

C#进阶

2022-07-22
solitaryclown
C#

1. C#高级内容

1.1. 多线程

1.1.1. 线程基础操作

1.1.1.1. new Thread和Start()

C#中新建一个线程的方法是创建一Thread实例,构造参数接收一个方法指针(委托),调用Start()让线程开始执行:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestThread
{
    public delegate int TakesAWhileDelegate(int data, int ms);
    class Program
    {
        /*
         多线程测试
         */
        static void Main(string[] args)
        {
            Thread t = new Thread(WriteB);
            t.Name = "t1";
            t.Start();
            //Console.WriteLine(t.IsAlive);
            Thread.CurrentThread.Name = "主线程";
            
            WriteA();
            //Console.WriteLine(t.IsAlive);
            Console.ReadLine();
        }
        static void WriteA()
        {
            Console.WriteLine($"我是线程[{Thread.CurrentThread.Name}],我执行任务:打印字符A");
            for (int i = 0; i < 100; i++)
            {
                Console.Write("A");
            }
        }
        static void WriteB()
        {
            Console.WriteLine($"我是线程[{Thread.CurrentThread.Name},我执行任务:打印字符B");
            for (int i = 0; i < 100; i++)
            {
                Console.Write("B");
            }
        }

    }
}

运行结果:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Thread的两个构造方法:

  • public Thread(ThreadStart start):start为无参的委托类型,适用于目标任务方法无参数的情况。
  • public Thread(ParameterizedThreadStart start):start是有参的委托类型,参数为object类型,执行时通过Start(object param)传递实参。

1.1.1.2. Join()和Sleep()

  • Sleep(int ms):当前线程休眠ms毫秒
  • t.Join():当前线程等待线程t运行结束

线程在Sleep和Join期间的状态是WaitJoinSleepThread.CurrentThread.State获取当前线程的状态

1.1.1.3. 线程安全问题

C#提供lock语句块控制线程间同步。 语法:

lock(lock对象){
    ...
}

示例:

namespace TestThread
{
    class Program3
    {
        bool _done;
        object _lock = new object();
        static void Main(string[] args)
        {
            Program3 p = new Program3();
            /*
             * 线程不安全
            new Thread(p.Go).Start();
            p.Go();
            */

            /*线程安全*/
            new Thread(p.GoWithLock).Start();
            p.GoWithLock();

            Console.ReadKey();
        }

        private  void Go()
        {
            if (!_done)
            {
                Console.WriteLine("Done.");
                _done = true;
            }
        }

        //使用lock解决线程安全问题
        private void GoWithLock()
        {
            lock (this)
            {
                if (!_done)
                {
                    Console.WriteLine("Done.");
                    _done = true;
                }
            }
        }
    }
}

运行结果:

  • 使用lock
      Done.
    
  • 不使用lock
      Done.
      Done.
    

1.1.1.4. 多线程异常处理

一段示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestThreadEx
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                new Thread(Go).Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("异常被抛出:" + e.Message);
            }
        }
        private static void Go()
        {
            throw null;
        }
    }
}

在上面的程序中,Go方法抛出异常,但Main()方法中的catch并不会捕获到这个异常,这个异常直接被抛给了运行时环境。

正确的解决方法:在Go方法中捕获可能抛出的异常。 改正:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestThreadEx
{
    class Program2
    {
        static void Main(string[] args)
        {
            new Thread(Go).Start();
            Console.ReadKey();
        }
        private static void Go()
        {
            try
            {
                throw null;
            }
            catch (Exception e)
            {
                Console.WriteLine("异常被抛出:" + e.Message);
            }
        }
    }
}

运行结果:

异常被抛出:未将对象引用设置到对象的实例。

1.1.1.5. background线程和foreground线程

  • 后台线程:即background线程,后台线程不会阻止程序的终止,当所有前台线程结束,所有后台线程都会马上结束
  • 前台线程:即foreground线程,只要有一个前台线程还在运行,程序就不会终止。

1.1.2. Signaling

即信号量 示例:

using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestSignal
{
    class Program
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof(Program));
        static void Main(string[] args)
        {
            var signal= new ManualResetEvent(false);

            new Thread(() =>
            {
                logger.Info("Waiting for signal");
                signal.WaitOne();
                signal.Dispose();
                logger.Info("Got the signal");
            }).Start();

            Thread.Sleep(2000);
            signal.Set();
        }
    }
}

输出结果:

2022-07-22 18:19:27,665 [4] INFO  TestSignal.Program - Waiting for signal
2022-07-22 18:19:29,665 [4] INFO  TestSignal.Program - Got the signal

1.1.3. 富客户端中的多线程

富客户端包括WPF、Metro、WinForm等应用。

1.1.3.1. 问题和解决方法

在富客户端应用中,控件和UI元素只能由创建它们的线程去控制(一般是main UI线程),如果在非UI线程中想要更新控件的内容,需要把请求转发给UI线程,技术术语叫做marshal,解决方法如下:

1.1.3.1.1. 低级方法
  1. 在WPF应用中,在UI元素的Dispather对象上调用BeginInvoke()或者Invoke()方法
  2. 在WinForm应用中,在控件上调用BeginInvoke()或者Invoke()

BeginInvoke()Invoke()功能是一样的,区别在于Invoke()会阻塞当前线程,而BeginInvoke()不会

1.1.3.1.2. 高级方法

除了调用BeginInvoke()和Invoke()方法,还可以使用SynchronizationContext来解决非UI线程更新控件内容的问题:示例代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestMarshal
{
    public partial class Form1 : Form
    {
        SynchronizationContext _uiSyncContext;
        public Form1()
        {
            InitializeComponent();
            this.lbTime_1.Text = this.lbTime_1.Name+":"+DateTime.Now.ToString();
            _uiSyncContext = SynchronizationContext.Current;
            new Thread(Work).Start();
        }

        private void Work()
        {
            Thread.Sleep(5000);
            UpdateMessage("The answer");
        }

        private void UpdateMessage(string msg)
        {
            // Marshal the delegate to the UI thread:
            _uiSyncContext.Post((m) => lbTime_2.Text = (string)m,this.lbTime_2.Name+":"+DateTime.Now.ToString());
        }
    }
}

显示效果: jOwq9x.png

1.1.4. 线程池

需要注意的几个点:

  • 线程池中的线程不能设置Name
  • 线程池中的线程总是background线程
  • 阻塞的池中线程可能会降低性能

使用线程池的好处有:

  1. 线程预先创建,任务到达时立即执行,提高效率
  2. 避免手动创建过多的活跃线程(数量超过CPU核心数)导致上下文切换带来的时间浪费

1.1.4.1. 使用

使用线程池最简单的方法是调用Task.Run(),如下:

using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestThreadPool
{
    class Program
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof(Program));

        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main";
            logger.Info($"线程[{Thread.CurrentThread.Name}]开始执行");
            Task.Run(() =>
            {
                logger.Info($"线程[{Thread.CurrentThread.Name}]开始执行");
                Thread.Sleep(2000);
                logger.Info($"线程[{Thread.CurrentThread.Name}]结束执行");
            });

            Thread.Sleep(2000);
            logger.Info($"线程[{Thread.CurrentThread.Name}]结束执行");

        }
    }
}

执行结果:

2022-07-22 20:15:40,599 [Main] INFO  TestThreadPool.Program - 线程[Main]开始执行
2022-07-22 20:15:40,615 [4] INFO  TestThreadPool.Program - 线程[]开始执行
2022-07-22 20:15:42,626 [4] INFO  TestThreadPool.Program - 线程[]结束执行
2022-07-22 20:15:42,626 [Main] INFO  TestThreadPool.Program - 线程[Main]结束执行

1.1.5. Task

Task作为并发库的一部分,是在.NET framework 4.0中发布的。

Task默认使用线程池中的线程执行任务。 Task的几个主要方法如下:

  1. Task Run():运行任务,有多个重载形式
    • 应用场景 默认情况下,CLR在池中线程运行task,因此适合较短运行时间的任务,如果任务时间长且是阻塞操作,可以调用下面的方法:
      Task task = Task.Factory.StartNew (() => ...,
                                    TaskCreationOptions.Longrunning);
      
    • 返回值 ```csharp private static void TestTaskResult() { Task task = Task.Run( () => { logger.Debug("start..."); Thread.Sleep(3000); logger.Debug("end..."); return 3; }); //task.Wait(); logger.Debug($"task执行结果:{task.Result}"); }

    运行结果: 2022-07-23 08:42:37,689 [4] DEBUG TestTask.Program - start… 2022-07-23 08:42:40,697 [4] DEBUG TestTask.Program - end… 2022-07-23 08:42:40,697 [1] DEBUG TestTask.Program - task执行结果:3 ```

    当task未完成时,task.Result会阻塞当前线程,因此task.Wait()不是必要的

  2. void Wait():当前线程等待task线程执行完 示例:
    using log4net;
     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading;
     using System.Threading.Tasks;
    
     namespace TestTask
     {
         class Program
         {
    
             private static readonly ILog logger = LogManager.GetLogger(typeof(Program));
             static void Main(string[] args)
             {
                 /*Task应用*/
                 TestWait();
             }
    
             private static void TestWait()
             {
                 Task task = Task.Run(() =>
                 {
                     Thread.Sleep(2000);
                     logger.Debug("Foo");
                 });
                 logger.Debug(task.IsCompleted);
                 task.Wait();
                 logger.Debug(task.IsCompleted);
             }
         }
            
     }
    

    运行结果:

     2022-07-23 08:25:20,747 [1] DEBUG TestTask.Program - False
     2022-07-23 08:25:22,758 [4] DEBUG TestTask.Program - Foo
     2022-07-23 08:25:30,935 [1] DEBUG TestTask.Program - True
    

1.1.5.1. Task和异常

不同于直接使用Thread,Task可以方便地传播异常。 Task在执行时发生异常会将异常抛给调用task.Wati()方法或访问task.Result属性的线程。 示例:

private static void TestTaskException()
{
    Task task = Task.Run(() => {
        logger.Debug("开始执行.");
        Thread.Sleep(2000);
        logger.Debug("抛出了一个异常.");
        throw null;
    });
    try
    {
        task.Wait();
    }
    catch (Exception e)
    {
        logger.Debug($"捕获到一个异常{e.Message}");
    }
}

运行结果:
2022-07-23 08:59:33,010 [4] DEBUG TestTask.Program - 开始执行.
2022-07-23 08:59:35,031 [4] DEBUG TestTask.Program - 抛出了一个异常.
2022-07-23 08:59:35,041 [1] DEBUG TestTask.Program - 捕获到一个异常发生一个或多个错误。

1.1.6. C#5.0 await和async

1.2. 反射


上一篇 Vm相关概念

下一篇 WinForm编程

Comments

Content