C# 多线程之Task高级用法

Parallel

Parallel并发执行多个Action,主线程会参与计算—阻塞界面,等于TaskWaitAll+主线程计算。

            {
                Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"),
                    () => this.DoSomethingLong("btnParallel_Click_2"),
                    () => this.DoSomethingLong("btnParallel_Click_3"),
                    () => this.DoSomethingLong("btnParallel_Click_4"),
                    () => this.DoSomethingLong("btnParallel_Click_5"));
            }
            {
                Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
            }
            {
                Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
            }
            {
                ParallelOptions options = new ParallelOptions();
                options.MaxDegreeOfParallelism = 3; //最大线程数量
                Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
            }

主线程不参与计算,不阻塞界面。如下:

            {
                //不阻塞界面
                Task.Run(() =>
                {
                    ParallelOptions options = new ParallelOptions();
                    options.MaxDegreeOfParallelism = 3;
                    Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
                });
            }

多线程异常处理

在子线程内部,发生异常之后,异常找不到了,那么异常到那儿去了?必然是被吞掉了;

如何把异常捕捉到呢?

  • a.list 存入所有的Task
  • b.Task.WaitAll()可以捕捉到AggregateException类型异常;
  • c.可以多个Cath来捕捉异常,异常–先具体再全部
  • d.可以通过aex.InnerExceptions获取到多线程中所有的异常
                {
                    try
                    {
                        List<Task> taskList = new List<Task>();
                        for (int i = 0; i < 50; i++)
                        {
                            string name = $"btnTaskAdvanced_Click_{i}";
                            taskList.Add(Task.Run(() =>
                            {
                                if (name.Equals("btnTaskAdvanced_Click_8"))
                                {
                                    throw new Exception("btnTaskAdvanced_Click_8  异常了。。。");
                                }
                                else if (name.Equals("btnTaskAdvanced_Click_13"))
                                {
                                    throw new Exception("btnTaskAdvanced_Click_13  异常了。。。");
                                }
                                else if (name.Equals("btnTaskAdvanced_Click_25"))
                                {
                                    throw new Exception("btnTaskAdvanced_Click_25  异常了。。。");
                                }
                                Console.WriteLine($"this is {name} 成功, ThreadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                            }));
                        }
                        Task.WaitAll(taskList.ToArray());
                    }
                    catch (AggregateException aex)
                    {
                        foreach (var aexception in aex.InnerExceptions)
                        {
                            Console.WriteLine(aexception.Message);
                        }
                        Console.WriteLine(aex.Message);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }

线程取消

发生异常是个很尴尬的事儿,如果多个线程同时执行业务,如果有一个线程异常了,其实对整个业务链来说是不完美的;需要一些应对策略,可能就需要让其他的线程停止下来,重新操作;

多线程中如果有一个线程执行失败,需要取消其他的线程;

50个线程:其中有一个线程启动了,其他别的线程还没启动;刚好就这个启动的线程就异常了;还没有启动的这49个线程,我还有必要让你启动吗?

全局变量
                        bool isOk = true;
                        for (int i = 0; i < 50; i++)
                        {
                            string name = $"btnTaskAdvanced_Click_{i}";
                            Task.Run(() =>
                            {
                                if (isOk)
                                {
                                    Console.WriteLine($"{name}  Start.....");
                                }
                                else
                                {
                                    throw new AggregateException(); //停止了线程
                                }
                                if (name.Equals("btnTaskAdvanced_Click_8"))
                                {
                                    isOk = false;
                                    throw new Exception("btnTaskAdvanced_Click_8  异常了。。。");
                                }
                                else if (name.Equals("btnTaskAdvanced_Click_13"))
                                {
                                    throw new Exception("btnTaskAdvanced_Click_13  异常了。。。");
                                }
                                else if (name.Equals("btnTaskAdvanced_Click_25"))
                                {
                                    throw new Exception("btnTaskAdvanced_Click_25  异常了。。。");
                                }

                                if (isOk)
                                {
                                    Console.WriteLine($"{name}  End.....");
                                }
                                else
                                {
                                    //这里也可以回滚
                                    throw new AggregateException(); //停止了线程
                                }

                                Console.WriteLine($"this is {name} 成功, ThreadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                            });
                        }
CancellationTokenSource

CancellationTokenSource类中有一个属性:IsCancellationRequested,默认值为false;

还有一个Cancel()方法,只要是Cancel()执行,就可以把IsCancellationRequested指定为true; 可以重复调用Cancel()方法;

跟全局变量有点像,CancellationTokenSource是线程安全的;Cancel() 调用以后IsCancellationRequested指定为true不能再重置回来的;

以上+业务判断来实现线程取消;如果在Cancel 之前已经进入业务处理的线程是无法停止下来,所以在最后再判断一次,不让你正常运行结束;

                        try
                        {
                            CancellationTokenSource cts = new CancellationTokenSource();
                            List<Task> taskList = new List<Task>();
                            for (int i = 0; i < 100; i++)
                            {
                                Thread.Sleep(new Random().Next(100, 300));
                                string name = $"btnTaskAdvanced_Click_{i}";
                                taskList.Add(Task.Run(() =>
                               {
                                   //只要是执行到这里了,就是一个单独的线程;这里就可以开启事务

                                   if (!cts.IsCancellationRequested)
                                   {
                                       Console.WriteLine($"{name}  Start.....{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                   }
                                   else
                                   {
                                       Console.WriteLine($"{name} 失败了.....{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                       //可以事务回滚
                                       //throw new AggregateException(); //停止了线程
                                   }
                                   if (name.Equals("btnTaskAdvanced_Click_8"))
                                   {
                                       cts.Cancel(); //就可以把IsCancellationRequested指定为true; 
                                       throw new Exception("btnTaskAdvanced_Click_8  异常了。。。");
                                   }
                                   else if (name.Equals("btnTaskAdvanced_Click_13"))
                                   {
                                       cts.Cancel(); //就可以把IsCancellationRequested指定为true; 
                                       throw new Exception("btnTaskAdvanced_Click_13  异常了。。。");
                                   }
                                   else if (name.Equals("btnTaskAdvanced_Click_25"))
                                   {
                                       cts.Cancel(); //就可以把IsCancellationRequested指定为true; 
                                       throw new Exception("btnTaskAdvanced_Click_25  异常了。。。");
                                   }
                                   //如果有业务需要,其实也可以直接Cancel(),其他的线程也都停止下来了;

                                   if (!cts.IsCancellationRequested)
                                   {
                                       Console.WriteLine($"{name}  End.....{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                   }
                                   else
                                   {
                                       //这里也可以回滚
                                       //throw new AggregateException(); //停止了线程
                                       Console.WriteLine($"{name} 失败了.....{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                   }
                                   //Console.WriteLine($"this is {name} 成功, ThreadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                               }, cts.Token)); //只需要把cts.Token 给Task.run()

                            }

                            Task.WaitAll(taskList.ToArray());
                        }
                        catch (AggregateException aex)
                        {
                            foreach (var exception in aex.InnerExceptions)
                            {
                                Console.WriteLine(exception.Message);
                            }
                        }

临时变量

1.线程的开启是非阻塞的,延迟启动;
2.这里是循环5次,代码执行很快,开启线程不阻塞

for (int i = 0; i < 5; i++)
{
    int k = i;
    Task.Run(() =>
    {
        Console.WriteLine($"ThreadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}_i={i}__ k={ k}");
    });
}

线程安全

如果你的代码在单线程情况下执行的结果和多线程执行的结果完全一致,那么这就是线程安全的;

线程安全问题一般发生在全局变量、共享变量、硬盘文件,只要是多线程都能访问和修改的公共数据;

因为是多线程操作,操作同时进行,可能会出现覆盖;

怎么解决线程安全:

  • lock -----可以,但是极力不推荐,因为lock是反多线程;
  • 使用框架提供的线程安全数据结构
    • 引入System.Collections.Concurrent命名—线程安全数据结构
    • 把之前的非线程安全的数据结构更换成以下数据结构即可;
      • 1.BlockingCollection< T> 为实现 IProducerConsumerCollection< T> 的线程安全集合提供阻塞和限制功能。
      • 2.ConcurrentBag< T> 表示对象的线程安全的无序集合。
      • 3.ConcurrentDictionary 表示可由多个线程同时访问的键值对的线程安全集合。
      • 4.ConcurrentQueue< T> 表示线程安全的先进先出 (FIFO) 集合。
      • 5.ConcurrentStack< T> 表示线程安全的后进先出 (LIFO) 集合。
      • 6.OrderablePartitioner< TSource> 表示将一个可排序数据源拆分成多个分区的特定方式。
      • 7.Partitioner 提供针对数组、列表和可枚举项的常见分区策略。
      • 8.Partitioner< TSource> 表示将一个数据源拆分成多个分区的特定方式。
					List<int> tasklist = new List<int>();
                    BlockingCollection<int> blockinglist = new BlockingCollection<int>();
                    ConcurrentBag<int> conocurrentbag = new ConcurrentBag<int>();
                    ConcurrentDictionary<string, int> concurrentDictionary = new ConcurrentDictionary<string, int>();
                    ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();
                    ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
                    //OrderablePartitioner OrderablePartitioner = new OrderablePartitioner();
                    //Partitioner partitionerArray=new 
                    //Partitioner
                    for (int i = 0; i < 10000; i++)
                    {
                        int k = i;
                        Task.Run(() =>
                        {
                            tasklist.Add(k);
                            blockinglist.Add(k);
                            conocurrentbag.Add(k);
                            concurrentDictionary.TryAdd($"concurrentDictionary_{k}", k);
                            concurrentQueue.Enqueue(k);
                            concurrentStack.Push(k);
                        });
                    }
                    Console.WriteLine($"tasklist:{tasklist.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
                    Console.WriteLine($"blockinglist{blockinglist.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
                    Console.WriteLine($"conocurrentbag{conocurrentbag.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
                    Console.WriteLine($"concurrentDictionary{concurrentDictionary.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
                    Console.WriteLine($"concurrentQueue{concurrentQueue.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
                    Console.WriteLine($"concurrentStack{concurrentStack.Count()}");
                    Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

Lock,锁—排他性,独占;
标准锁:锁对象,引用类型,不要锁string,可能会冲突;
强烈规定:以后大家使用lock,按照这种标准格式;private static readonly object obj_Forom = new object(); int,string,this 都不要;

        		private static readonly object obj_Forom = new object(); 
                {
                    List<int> tasklist = new List<int>();
                    for (int i = 0; i < 10000; i++)
                    {
                        int k = i;
                        Task.Run(() =>
                        {
                            lock (obj_Forom) //只允许一个线程从这里经过,这不就是单线程了吗?  反多线程;
                            {
                                tasklist.Add(k);
                            }
                        });
                    }
                    Console.WriteLine($"tasklist:{tasklist.Count()}");
                }

线程安全问题一般发生在全局变量、共享变量、硬盘文件,只要是多线程都能访问和修改的公共数据;
既然多线程去操作会有线程安全问题,那么就拆分数据源,然后每一个线程对标于单独的某一个数据块;
多线程操作完毕以后,再合并数据。

你可能感兴趣的:(C#,c#)