• C# 多线程


    转自https://www.cnblogs.com/yaosj/p/10342883.html

    目录


    简介:

    Task 对象是一种的中心思想基于任务的异步模式首次引入.NET Framework 4 中。 因为由执行工作Task对象通常以异步方式执行线程池线程上而不是以同步方式在主应用程序线程中,可以使用Status属性,并将IsCanceled, IsCompleted,和IsFaulted属性,以确定任务的状态。

    一.Task的创建

    1.创建Task类

    (1)

    1
    2
    3
    4
    5
    Task task = new Task(() =>
    {
        Console.WriteLine("hello world!");
    });
    task.Start(); 

    (2)

    1
    2
    3
    4
    new Task(() =>
    {
        Console.WriteLine("hello world!");
    }).Start();

    (3)带参数

    1
    2
    3
    4
    new Task(x =>
    {
        Console.WriteLine(x.ToString());
    }, "hello world!").Start();

    (4)带返回值

    1
    2
    3
    4
    5
    6
    Task<string> t = new Task<string>(x =>
    {
        return x.ToString();
    }, "hello world!");
    t.Start();
    Console.WriteLine(t.Result);

      

    2.Task.Factory.StartNew

    (1)

    1
    2
    3
    4
    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("hello world!");
    });

    (2)带参数

    1
    2
    3
    4
    Task.Factory.StartNew(x =>
    {
        Console.WriteLine(x.ToString());
    }, "hello world!");

      

    (3)带返回值

    1
    2
    3
    4
    Task<string> t = Task.Factory.StartNew<string>(() =>
    {
        return "hello world!";
    });<br>        Console.WriteLine(t.Result);

      

    3.Task.Run

    1
    2
    3
    4
    Task.Run(() =>
    {
        Console.WriteLine("hello world!");
    });

    4.TaskStatus

    1
    2
    3
    4
    5
    6
    7
    8
    Created = 0, //该任务已初始化,但尚未被计划。
    WaitingForActivation = 1, //该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
    WaitingToRun = 2,//该任务已被计划执行,但尚未开始执行。
    Running = 3, //该任务正在运行,但尚未完成。
    WaitingForChildrenToComplete = 4,//该任务已完成执行,正在隐式等待附加的子任务完成。
    RanToCompletion = 5,//已成功完成执行的任务。
    Canceled = 6, //该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的CancellationToken 发出了信号。 有关详细信息,请参阅任务取消。
    Faulted = 7 //由于未处理异常的原因而完成的任务。

      

    二. TaskCreationOptions

     Task.Factory.StartNew和创建Task类可以带TaskCreationOptions参数而Task.Run不可以带

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //
    // 摘要:
    //     指定应使用默认行为。
    None = 0,
    //
    // 摘要:
    //     提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
    PreferFairness = 1,
    //
    // 摘要:
    //     指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 System.Threading.Tasks.TaskScheduler
    //     提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。
    LongRunning = 2,
    //
    // 摘要:
    //     指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 System.Threading.Tasks.TaskContinuationOptions.AttachedToParent
    //     选项以便将父任务和子任务同步。 请注意,如果使用 System.Threading.Tasks.TaskCreationOptions.DenyChildAttach
    //     选项配置父任务,则子任务中的 System.Threading.Tasks.TaskCreationOptions.AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。
    //     有关详细信息,请参阅附加和分离的子任务。
    AttachedToParent = 4,
    //
    // 摘要:
    //     指定任何尝试作为附加的子任务执行(即,使用 System.Threading.Tasks.TaskCreationOptions.AttachedToParent
    //     选项创建)的子任务都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务。
    DenyChildAttach = 8,
    //
    // 摘要:
    //     防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default
    //     当前计划程序。
    HideScheduler = 16

    1. LongRunning

    任务是长时间任务,就需要用LongRunning,可能会创建一个非线程池线程来执行该任务,防止阻塞线程池队列中的其他线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private static void fun8()
    {
        Task.Factory.StartNew(() =>
        {
            Console.WriteLine($"task1.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
        });
     
        Task.Factory.StartNew(() =>
        {
            Console.WriteLine($"task2.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
        }, TaskCreationOptions.LongRunning);
    }

     运行结果:

     2. 父子任务(AttachedToParent,DenyChildAttach)

    AttachedToParent:将子任务附加到父任务上,表现为:附加到父任务上的所有子任务都结束,父任务才结束

    DenyChildAttach:不允许子任务附加到父任务上

    (1)子任务不附加到父任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static void fun5()
    {
        Task t = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("parent");
     
            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("child");
            });
        });
        t.ContinueWith(x =>
        {
            Console.WriteLine("parent over");
        });
    }

     运行结果:

    (2)子任务附加到父任务上,使用AttachedToParent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static void fun6()
    {
        Task t = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("parent");
     
            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("child");
            },TaskCreationOptions.AttachedToParent);
        });
        t.ContinueWith(x =>
        {
            Console.WriteLine("parent over");
        });
    }

     运行结果:

    (3)拒绝子任务附加到父任务上,使用DenyChildAttach

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static void fun7()
    {
        Task t = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("parent");
     
            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("child");
            }, TaskCreationOptions.AttachedToParent);
        }, TaskCreationOptions.DenyChildAttach);
        t.ContinueWith(x =>
        {
            Console.WriteLine("parent over");
        });
    }

      运行结果:

     三.CancellationToken 取消任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    private static void fun4()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken Token = cancellationTokenSource.Token;
        //结束任务回调
        Token.Register(() =>
        {
            Console.WriteLine("canceled");
        });
     
        Task.Factory.StartNew(() =>
        {
            try
            {
                while (true)
                {
                    Console.WriteLine("hello world!");
                    Thread.Sleep(100);
                    Token.ThrowIfCancellationRequested();
                }
            }
            catch (OperationCanceledException ocex)
            {
            }
            catch (ObjectDisposedException odex)
            {
            }
            catch (Exception ex)
            {
            }
     
        }, Token);
     
        Thread.Sleep(1000);
     
        cancellationTokenSource.Cancel();
    }

     

    执行结果:

    当执行cancellationTokenSource.Cancel()后,任务进行到Token.ThrowIfCancellationRequested()代码后,throw出OperationCanceledException异常,才结束任务并执行cancel回调

     四.方法

    Wait 等待 System.Threading.Tasks.Task 完成执行过程
    WaitAll  等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程
    WaitAny  等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程
    WhenAll  创建一个任务,该任务将在所有 System.Threading.Tasks.Task 对象都完成时完成
    WhenAny  任何提供的任务已完成时,创建将完成的任务
    ContinueWith  创建一个在目标 System.Threading.Tasks.Task 完成时异步执行的延续任务

    1 Wait

    阻塞当前线程,等待任务执行完成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //等待 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
    public bool Wait(int millisecondsTimeout);
    //等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前取消标记已取消,等待将终止。
    public void Wait(CancellationToken cancellationToken);
    //等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前超时间隔结束或取消标记已取消,等待将终止。      
    public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken);      
    //等待 System.Threading.Tasks.Task 完成执行过程。
    public void Wait();
    //等待 System.Threading.Tasks.Task 在指定的时间间隔内完成执行。
    public bool Wait(TimeSpan timeout);

    使用方式:

    1
    2
    3
    t.Wait();//无限等待
    t.Wait(100);//等待100ms
    t.Wait(TimeSpan.FromMilliseconds(1500));//等待1500ms

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private static void fun9()
    {
        Task t = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000);
            Console.WriteLine("task");
        });
        t.Wait();
        Console.WriteLine("main");
    }

     运行结果:

    2.WaitAll 

    阻塞当前线程,等待所有任务执行完成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程。
    public static void WaitAll(params Task[] tasks);
    //等待所有提供的可取消 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
    public static bool WaitAll(Task[] tasks, TimeSpan timeout);
    //等待所有提供的 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
    public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
    //等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
    public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
    //等待提供的所有 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消等待。
    public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);

      使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private static void fun10()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("task1");
        });
     
        Task t2 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(150);
            Console.WriteLine("task2");
        });
     
        //Task.WaitAll(t1, t2);
        //Task.WaitAll(new Task[] { t1, t2 }, 200);
        Task.WaitAll(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
        Console.WriteLine("main");
    }

      

     运行结果:

    3.WaitAny

    阻塞当前线程,等待任一任务执行完成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程。
    public static int WaitAny(params Task[] tasks);
    //等待任何提供的 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
    public static int WaitAny(Task[] tasks, TimeSpan timeout);
    //等待任何提供的 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行。
    public static int WaitAny(Task[] tasks, int millisecondsTimeout);
    //等待提供的任何 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
    public static int WaitAny(Task[] tasks, CancellationToken cancellationToken);
    //等待提供的任何 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消标记取消。
    public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);

     使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private static void fun11()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("task1");
        });
     
        Task t2 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(150);
            Console.WriteLine("task2");
        });
     
        //Task.WaitAny(t1, t2);
        //Task.WaitAny(new Task[] { t1, t2 }, 200);
        Task.WaitAny(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
        Console.WriteLine("main");
    }

     结果:

    4.WhenAll

    不阻塞当前线程,等待所有任务执行完成后,可以进行ContinueWith

    1
    2
    3
    4
    5
    6
    7
    8
    //创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task 对象都完成时完成。
    public static Task WhenAll(IEnumerable<Task> tasks);
    //创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task 对象都完成时完成。
    public static Task WhenAll(params Task[] tasks);
    //创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
    public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);
    //创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
    public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);

      使用:

    (1)不带返回值 

    1
    public static Task WhenAll(params Task[] tasks);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private static void fun12()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("task1");
        });
     
        Task t2 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(150);
            Console.WriteLine("task2");
        });
     
        Task.WhenAll(t1, t2).ContinueWith(t =>
        {
            Console.WriteLine("WhenAll");
        });
     
        Console.WriteLine("main");
    }

      执行结果:

    (2)带返回值

    1
    public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private static void fun13()
    {
        Task<string> t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("task1");
            return "task1";
        });
     
        Task<string> t2 = Task.Factory.StartNew<string>(() =>
        {
            Thread.Sleep(150);
            Console.WriteLine("task2");
            return "task2";
        });
     
        Task.WhenAll(new Task<string>[] { t1, t2 }).ContinueWith(t =>
        {
            string s = "WhenAll:";
            foreach (string item in t.Result)
            {
                s += item;
            }
            Console.WriteLine(s);
        });
     
        Console.WriteLine("main");
    }

      执行结果:

    5.WhenAny

    1
    2
    3
    4
    5
    6
    7
    8
    //任何提供的任务已完成时,创建将完成的任务。
    public static Task<Task> WhenAny(params Task[] tasks);
    //任何提供的任务已完成时,创建将完成的任务。
    public static Task<Task> WhenAny(IEnumerable<Task> tasks);
    //任何提供的任务已完成时,创建将完成的任务。
    public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
    //任何提供的任务已完成时,创建将完成的任务。
    public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);

     使用:

    (1)不带参数

    1
    public static Task<Task> WhenAny(params Task[] tasks);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private static void fun14()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("task1");
        });
     
        Task t2 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(150);
            Console.WriteLine("task2");
        });
     
        Task.WhenAny(t1, t2).ContinueWith(t =>
        {
            Console.WriteLine("WhenAny1");
        });
     
        Console.WriteLine("main");
    }

      运行结果:

    (2)带参数:

    1
    public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    private static void fun15()
            {
                Task<string> t1 = Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(100);
                    Console.WriteLine("task1");
                    return "response task1";
                });
     
                Task<string> t2 = Task.Factory.StartNew<string>(() =>
                {
                    Thread.Sleep(150);
                    Console.WriteLine("task2");
                    return "response task2";
                });
     
                Task.WhenAny(new Task<string>[] { t1, t2 }).ContinueWith(t =>
                {
                    t.Result.ContinueWith(tt =>
                    {
                        Console.WriteLine(tt.Result);
                    });
                });
     
                Console.WriteLine("main");
            } 

    运行结果:

    6.ContinueWith

    相当于回调

    6.1使用

    (1)使用lambda表达式方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private static void fun1()
    {
        Console.WriteLine("start");
        Task<string> task1 = new Task<string>(() =>
        {
            Console.WriteLine("task0");
            return "task1";
        });
        Task<string> task2 = task1.ContinueWith(t =>
        {
            Console.WriteLine(t.Result);
            return "task2";
        });
        task1.Start();
        Console.WriteLine("end");
        Console.ReadKey();
    }

    2.使用函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private static void fun2()
           {
               Console.WriteLine("start");
               Task<string> task1 = new Task<string>(doTask1);
               Task<string> task2 = task1.ContinueWith(doTask2);
               task1.Start();
               Console.WriteLine("end");
               Console.ReadKey();
           }
     
           private static string doTask1()
           {
               Console.WriteLine("task0");
               return "task1";
           }
     
           private static string doTask2(Task<string> t)
           {
               Console.WriteLine(t.Result);
               return "task2";
           }

      运行结果:

     6.2 ContineWith和task可能不在同一线程上

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private static void fun16()
           {
               Task.Factory.StartNew(() =>
                {
                    Console.WriteLine($"task {Thread.CurrentThread.ManagedThreadId}");
                }).ContinueWith(t =>
                {
                    Console.WriteLine($"ContinueWith {Thread.CurrentThread.ManagedThreadId}");
                });
           }

      运行结果:

     七.TaskFactory类

    方法:

    StartNew 创建并启动任务
    ContinueWhenAll 创建一个延续任务,该任务在一组指定的任务完成后开始
    ContinueWhenAny 创建一个延续 System.Threading.Tasks.Task,它将在提供的组中的任何任务完成后马上开始
    FromAsync 创建一个 System.Threading.Tasks.Task`1,表示符合异步编程模型模式的成对的开始和结束方法

    1.ContinueWhenAll

    相当于回调

    效果其实和WhenAll差不多,只不过ContineWhenAll采用了回调的方式

    使用:带返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    private static void fun17()
    {
        Task<string> t1 = Task.Factory.StartNew<string>(() =>
        {
            Console.WriteLine("task1");
            return "task1";
        });
     
        Task<string> t2 = Task.Factory.StartNew<string>(() =>
        {
            Console.WriteLine("task2");
            return "task2";
        });
     
        Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, t =>
        {
            string s = "ContinueWhenAll:";
            foreach (Task<string> item in t)
            {
                s += item.Result;
            }
            Console.WriteLine(s);
        });
    }

      运行结果:

     2.ContinueWhenAny

    相当于回调

    效果其实和WhenAny差不多,只不过ContineWhenAny采用了回调的方式

    使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private static void fun18()
    {
        Task<string> t1 = Task.Factory.StartNew<string>(() =>
        {
            Thread.Sleep(10);
            Console.WriteLine("task1");
            return "task1";
        });
     
        Task<string> t2 = Task.Factory.StartNew<string>(() =>
        {
            Console.WriteLine("task2");
            return "task2";
        });
     
        Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, t =>
        {
            Console.WriteLine($"{(t as Task<string>).Result} 先执行完");
        });
    }

      执行结果:

     3.FromAsync

    相当于异步委托的精简写法,其中ContinueWith相当于异步委托中的callback

    使用:

    public Task<TResult> FromAsync<TArg1, TResult>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod, TArg1 arg1, object state);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    static void Main(string[] args)
    {
        fun20();
     
        Console.WriteLine("Main");
        Console.ReadKey();
    }
     
    private static void fun20()
    {
        var func = new Func<stringstring>(x =>
        {
            Thread.Sleep(1000);
            Console.WriteLine(x);
            return "callback";
        });
     
        Task.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "func"null).ContinueWith(t =>
        {
            Console.WriteLine(t.Result);
        });
    }

      运行结果:

    参考:

    https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.7.2

    https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap?view=netframework-4.7.2

    https://www.cnblogs.com/leo_wl/archive/2012/03/03/2378695.html#_label2

  • 相关阅读:
    g4e基础篇#1 为什么要使用版本控制系统
    软件开发的自然属性
    定时器实现延时处理
    二分查找法
    php实现循环链表
    redis实现分布式锁
    RabbitMq初探——用队列实现RPC
    RabbitMq初探——发布与订阅
    RabbitMq初探——消息均发
    RabbitMq初探——消息持久化
  • 原文地址:https://www.cnblogs.com/KQNLL/p/11917005.html
Copyright © 2020-2023  润新知