来自 澳门新葡亰 2019-11-06 19:36 的文章
当前位置: 澳门新葡亰app > 澳门新葡亰 > 正文

澳门新葡亰官网APP2.EAP(基于事件的异步编程模式

目录

复习:

  • 1.1 简介
  • 1.2 创立义务
  • 1.3 使用职务实施基本的操作
  • 1.4 组合职分
  • 1.5 将APM情势转变为天职
  • 1.6 将EAP情势转换为天职
  • 1.7 完毕裁撤选项
  • 1.8 管理任务中的非常
  • 1.9 相互运转职务
  • 1.10 使用TaskScheduler配置职务试行
  • 参谋书籍
  • 我水平有限,假若不当应接各位商议指正!

其三章内容中大家提到了三种异步编制程序模型,这里大概复习一下,分别如下


1.APM(异步编制程序格局卡塔 尔(阿拉伯语:قطر‎:形如Beginxxx,Endxxx。

本种类首页链接:[C#二十四线程编制程序连串(意气风发卡塔尔- 简要介绍 ]

2.EAP(基于事件的异步编制程序方式卡塔尔国:那个我们在.net中央银行使到了BackgroudWorker组件,使用办法是因那一件事件绑定管理的方式。


3.TPL(基于职分的异步编制程序形式卡塔 尔(阿拉伯语:قطر‎:那几个就能够用到任务并行库。

1.1 简介

在前头的多少个章节中,就线程的采纳和二十四线程相关的内容开展了介绍。因为线程涉及到异步、同步、分外传递等难题,所以在项目中行使二十四线程的代价是相比高昂的,要求编写制定一大波的代码来达成科学和强壮性。

为了缓和那样局地的标题,在.Net Framework 4.0中引进了叁个有关一步操作的API。它叫做职务并行库(Task Parallel Library)。然后在.Net Framwork 4.5中对它举办了细微的精耕细作,本文的案例都以用新型版本的TPL库,并且大家还是能够使用C# 5.0的新特点await/async来简化TAP编制程序,当然那是然后才介绍的。

TPL内部接受了线程池,可是效用更高。在把线程归还回线程池早前,它会在同一线程中相继试行多少Task,那样防止了部分小职务上下文切换浪费时间片的主题素材。

职务是目的,在那之中封装了以异步情势施行的办事,不过委托也是包裹了代码的靶子。职分和信托的分别在于,委托是同步的,而职分是异步的。

在本章中,我们将会商量哪边运用TPL库来展开任务之间的咬公约步,怎样将余留的APM和EAP方式调换为TPL形式等等。

 

1.2 创立职分

在本节中,首假诺身先士卒了怎么成立三个任务。其重要行使了System.Threading.Tasks命名空间下的Task类。该类能够被实例化况且提供了生龙活虎组静态方法,可以方便飞快的成立任务。

在底下实例代码中,分别延时了两种数以万计的职务创制情势,并且创办职分是足以内定职务创设的选项,进而实现最优的始建方式。

TaskCreationOptions中总共有7个枚举,枚举是能够利用|运算符组合定义的。其枚举如下表所示。

成员名称 说明
AttachedToParent 指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意,如果使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。有关详细信息,请参阅附加和分离的子任务
DenyChildAttach 指定任何尝试作为附加的子任务执行(即,使用 AttachedToParent 选项创建)的子任务都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务
HideScheduler 防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 Default 当前计划程序。
LongRunning 指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 TaskScheduler 提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。
None 指定应使用默认行为。
PreferFairness 提示 TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
RunContinuationsAsynchronously 强制异步执行添加到当前任务的延续任务。请注意,RunContinuationsAsynchronously 成员在以 .NET Framework 4.6 开头的 TaskCreationOptions 枚举中可用。
static void Main(string[] args)
{
    // 使用构造方法创建任务
    var t1 = new Task(() => TaskMethod("Task 1"));
    var t2 = new Task(() => TaskMethod("Task 2"));

    // 需要手动启动
    t2.Start();
    t1.Start();

    // 使用Task.Run 方法启动任务  不需要手动启动
    Task.Run(() => TaskMethod("Task 3"));

    // 使用 Task.Factory.StartNew方法 启动任务 实际上就是Task.Run
    Task.Factory.StartNew(() => TaskMethod("Task 4"));

    // 在StartNew的基础上 添加 TaskCreationOptions.LongRunning 告诉 Factory该任务需要长时间运行
    // 那么它就会可能会创建一个 非线程池线程来执行任务  
    Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);

    ReadLine();
}

static void TaskMethod(string name)
{
    WriteLine($"任务 {name} 运行,线程 id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}.");
}

运作结果如下图所示。

澳门新葡亰官网APP 1

4.1 简介

1.3 使用任务实践基本的操作

在本节中,使用职务推行基本的操作,而且获得任务施行到位后的结果值。本节内容比较简单,在那不做过多介绍。

示范代码如下,在主线程中要获得结果值,常用的格局正是访问task.Result属性,如若义务线程还未有实施完成,那么会阻塞主线程,直到线程试行完。假设职分线程推行完成,那么将直接得到运算的结果值。

Task 3中,使用了task.Status来打字与印刷线程的意况,线程各个意况的切切实实意思,就要下风度翩翩节中牵线。

static void Main(string[] args)
{
    // 直接执行方法 作为参照
    TaskMethod("主线程任务");

    // 访问 Result属性 达到运行结果
    Task<int> task = CreateTask("Task 1");
    task.Start();
    int result = task.Result;
    WriteLine($"运算结果: {result}");

    // 使用当前线程,同步执行任务
    task = CreateTask("Task 2");
    task.RunSynchronously();
    result = task.Result;
    WriteLine($"运算结果:{result}");

    // 通过循环等待 获取运行结果
    task = CreateTask("Task 3");
    WriteLine(task.Status);
    task.Start();

    while (!task.IsCompleted)
    {
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(0.5));
    }

    WriteLine(task.Status);
    result = task.Result;
    WriteLine($"运算结果:{result}");

    Console.ReadLine();
}

static Task<int> CreateTask(string name)
{
    return new Task<int>(() => TaskMethod(name));
}

static int TaskMethod(string name)
{
    WriteLine($"{name} 运行在线程 {CurrentThread.ManagedThreadId}上. 是否为线程池线程 {CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(2));

    return 42;
}

运作结果如下,可以预知Task 1Task 2均是运营在主线程上,并非线程池线程。

澳门新葡亰官网APP 2

 

1.4 组合任务

在本节中,显示了职务之中一个有力的效应,那便是结合职责。通过整合职分可很好的汇报职务与职务之间的异步、同步关系,大大缩短了编制程序的难度。

构成职务主若是因而task.ContinueWith()task.WhenAny()task.WhenAll()等和task.GetAwaiter().OnCompleted()主意来促成。

在使用task.ContinueWith()方法时,须要静心它也可传递生龙活虎密密层层的枚举选项TaskContinuationOptions,该枚举选项和TaskCreationOptions恍如,其现实定义如下表所示。

成员名称 说明
AttachedToParent 如果延续为子任务,则指定将延续附加到任务层次结构中的父级。 只有当延续前面的任务也是子任务时,延续才可以是子任务。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 TaskContinuationOptions.AttachedToParent 选项以便将父任务和子任务同步。请注意,如果使用 DenyChildAttach 选项配置父任务,则子任务中的 AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。有关更多信息,请参见Attached and Detached Child Tasks
DenyChildAttach 指定任何使用 TaskCreationOptions.AttachedToParent 选项创建,并尝试作为附加的子任务执行的子任务(即,由此延续创建的任何嵌套内部任务)都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务
ExecuteSynchronously 指定应同步执行延续任务。 指定此选项后,延续任务在导致前面的任务转换为其最终状态的相同线程上运行。如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。 如果前面任务的 CancellationTokenSource 已在一个 finally(在 Visual Basic 中为 Finally)块中释放,则使用此选项的延续任务将在该 finally 块中运行。 只应同步执行运行时间非常短的延续任务。由于任务以同步方式执行,因此无需调用诸如 Task.Wait 的方法来确保调用线程等待任务完成。
HideScheduler 指定由延续通过调用方法(如 Task.RunTask.ContinueWith)创建的任务将默认计划程序 (TaskScheduler.Default) 视为当前的计划程序,而不是正在运行该延续的计划程序。
LazyCancellation 在延续取消的情况下,防止延续的完成直到完成先前的任务。
LongRunning 指定延续将是长期运行的、粗粒度的操作。 它会向 TaskScheduler 提示,过度订阅可能是合理的。
None 如果未指定延续选项,应在执行延续任务时使用指定的默认行为。 延续任务在前面的任务完成后以异步方式运行,与前面任务最终的 Task.Status 属性值无关。 如果延续为子任务,则会将其创建为分离的嵌套任务。
NotOnCanceled 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
NotOnFaulted 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。 此选项对多任务延续无效。
NotOnRanToCompletion 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
OnlyOnCanceled 指定只应在延续前面的任务已取消的情况下安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Canceled,则前面的任务会取消。 此选项对多任务延续无效。
OnlyOnFaulted 指定只有在延续任务前面的任务引发了未处理异常的情况下才应安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.Faulted,则前面的任务会引发未处理的异常。OnlyOnFaulted 选项可保证前面任务中的 Task.Exception 属性不是 null。 你可以使用该属性来捕获异常,并确定导致任务出错的异常。 如果你不访问 Exception 属性,则不会处理异常。 此外,如果尝试访问已取消或出错的任务的 Result 属性,则会引发一个新异常。此选项对多任务延续无效。
OnlyOnRanToCompletion 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 如果前面任务完成的 Task.Status 属性是 TaskStatus.RanToCompletion,则前面的任务会运行直至完成。 此选项对多任务延续无效。
PreferFairness 提示 TaskScheduler 按任务计划的顺序安排任务,因此较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
RunContinuationsAsynchronously 指定应异步运行延续任务。 此选项优先于 TaskContinuationOptions.ExecuteSynchronously。

示范代码如下所示,使用ContinueWith()OnCompleted()办法结合了职务来运作,搭配差别的TaskCreationOptionsTaskContinuationOptions来贯彻差异的功力。

static void Main(string[] args)
{
    WriteLine($"主线程 线程 Id {CurrentThread.ManagedThreadId}");

    // 创建两个任务
    var firstTask = new Task<int>(() => TaskMethod("Frist Task",3));
    var secondTask = new Task<int>(()=> TaskMethod("Second Task",2));

    // 在默认的情况下 ContiueWith会在前面任务运行后再运行
    firstTask.ContinueWith(t => WriteLine($"第一次运行答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程: {CurrentThread.IsThreadPoolThread}"));

    // 启动任务
    firstTask.Start();
    secondTask.Start();

    Sleep(TimeSpan.FromSeconds(4));

    // 这里会紧接着 Second Task运行后运行, 但是由于添加了 OnlyOnRanToCompletion 和 ExecuteSynchronously 所以会由运行SecondTask的线程来 运行这个任务
    Task continuation = secondTask.ContinueWith(t => WriteLine($"第二次运行的答案是 {t.Result}. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}"),TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);

    // OnCompleted 是一个事件  当contiuation运行完成后 执行OnCompleted Action事件
    continuation.GetAwaiter().OnCompleted(() => WriteLine($"后继任务完成. 线程Id {CurrentThread.ManagedThreadId}. 是否为线程池线程 {CurrentThread.IsThreadPoolThread}"));

    Sleep(TimeSpan.FromSeconds(2));
    WriteLine();

    firstTask = new Task<int>(() => 
    {
        // 使用了TaskCreationOptions.AttachedToParent 将这个Task和父Task关联, 当这个Task没有结束时  父Task 状态为 WaitingForChildrenToComplete
        var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task",5), TaskCreationOptions.AttachedToParent);

        innerTask.ContinueWith(t => TaskMethod("Thrid Task", 2), TaskContinuationOptions.AttachedToParent);

        return TaskMethod("First Task",2);
    });

    firstTask.Start();

    // 检查firstTask线程状态  根据上面的分析 首先是  Running -> WatingForChildrenToComplete -> RanToCompletion
    while (! firstTask.IsCompleted)
    {
        WriteLine(firstTask.Status);

        Sleep(TimeSpan.FromSeconds(0.5));
    }

    WriteLine(firstTask.Status);

    Console.ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务 {name} 正在运行,线程池线程 Id {CurrentThread.ManagedThreadId},是否为线程池线程: {CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));

    return 42 * seconds;
}

运作结果如下图所示,与预期结果大器晚成律。个中使用了task.Status来打字与印刷职务运行的情状,对于task.Status的场所具体意思如下表所示。

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

澳门新葡亰官网APP 3

线程池也就是线程和客户之间的叁个抽象层,向技师隐蔽了运用线程的内幕,使得程序猿潜心管理程序逻辑,实际不是各样线程难点。

1.5 将APM形式转变为职分

在前头的章节中,介绍了依附IAsyncResult接口达成了BeginXXXX/EndXXXX艺术的就叫APM方式。APM情势极度古老,那么哪些将它转变为TAP格局吧?对于周边的三种APM情势异步义务,大家平常采纳使用Task.Factory.FromAsync()方法来贯彻将APM模式转换为TAP模式

亲自过问代码如下所示,比较容易不作过多介绍。

static void Main(string[] args)
{
    int threadId;
    AsynchronousTask d = Test;
    IncompatibleAsychronousTask e = Test;

    // 使用 Task.Factory.FromAsync方法 转换为Task
    WriteLine("Option 1");
    Task<string> task = Task<string>.Factory.FromAsync(d.BeginInvoke("异步任务线程", CallBack, "委托异步调用"), d.EndInvoke);

    task.ContinueWith(t => WriteLine($"回调函数执行完毕,现在运行续接函数!结果:{t.Result}"));

    while (!task.IsCompleted)
    {
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(0.5));
    }
    WriteLine(task.Status);
    Sleep(TimeSpan.FromSeconds(1));

    WriteLine("----------------------------------------------");
    WriteLine();

    // 使用 Task.Factory.FromAsync重载方法 转换为Task
    WriteLine("Option 2");

    task = Task<string>.Factory.FromAsync(d.BeginInvoke,d.EndInvoke,"异步任务线程","委托异步调用");

    task.ContinueWith(t => WriteLine($"任务完成,现在运行续接函数!结果:{t.Result}"));

    while (!task.IsCompleted)
    {
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(0.5));
    }
    WriteLine(task.Status);
    Sleep(TimeSpan.FromSeconds(1));

    WriteLine("----------------------------------------------");
    WriteLine();

    // 同样可以使用 FromAsync方法 将 BeginInvoke 转换为 IAsyncResult 最后转换为 Task
    WriteLine("Option 3");

    IAsyncResult ar = e.BeginInvoke(out threadId, CallBack, "委托异步调用");
    task = Task<string>.Factory.FromAsync(ar, _ => e.EndInvoke(out threadId, ar));

    task.ContinueWith(t => WriteLine($"任务完成,现在运行续接函数!结果:{t.Result},线程Id {threadId}"));

    while (!task.IsCompleted)
    {
        WriteLine(task.Status);
        Sleep(TimeSpan.FromSeconds(0.5));
    }
    WriteLine(task.Status);

    ReadLine();
}

delegate string AsynchronousTask(string threadName);
delegate string IncompatibleAsychronousTask(out int threadId);

static void CallBack(IAsyncResult ar)
{
    WriteLine("开始运行回调函数...");
    WriteLine($"传递给回调函数的状态{ar.AsyncState}");
    WriteLine($"是否为线程池线程:{CurrentThread.IsThreadPoolThread}");
    WriteLine($"线程池工作线程Id:{CurrentThread.ManagedThreadId}");
}

static string Test(string threadName)
{
    WriteLine("开始运行...");
    WriteLine($"是否为线程池线程:{CurrentThread.IsThreadPoolThread}");
    Sleep(TimeSpan.FromSeconds(2));

    CurrentThread.Name = threadName;
    return $"线程名:{CurrentThread.Name}";
}

static string Test(out int threadId)
{
    WriteLine("开始运行...");
    WriteLine($"是否为线程池线程:{CurrentThread.IsThreadPoolThread}");
    Sleep(TimeSpan.FromSeconds(2));

    threadId = CurrentThread.ManagedThreadId;
    return $"线程池线程工作Id是:{threadId}";
}

运作结果如下图所示。

澳门新葡亰官网APP 4

可是使用线程池也很复杂。有七个难题存在:

1.6 将EAP情势转换为天职

在上几章中有涉及,通过BackgroundWorker类经过事件的法子实现的异步,我们叫它EAP形式。那么哪些将EAP方式转变为天职吗?很简短,我们只需要经过TaskCompletionSource类,就可以将EAP格局调换为职分。

以身作则代码如下所示。

static void Main(string[] args)
{
    var tcs = new TaskCompletionSource<int>();

    var worker = new BackgroundWorker();
    worker.DoWork += (sender, eventArgs) =>
    {
        eventArgs.Result = TaskMethod("后台工作", 5);
    };

    // 通过此方法 将EAP模式转换为 任务
    worker.RunWorkerCompleted += (sender, eventArgs) =>
    {
        if (eventArgs.Error != null)
        {
            tcs.SetException(eventArgs.Error);
        }
        else if (eventArgs.Cancelled)
        {
            tcs.SetCanceled();
        }
        else
        {
            tcs.SetResult((int)eventArgs.Result);
        }
    };

    worker.RunWorkerAsync();

    // 调用结果
    int result = tcs.Task.Result;

    WriteLine($"结果是:{result}");

    ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务{name}运行在线程{CurrentThread.ManagedThreadId}上. 是否为线程池线程{CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));

    return 42 * seconds;
}

运作结果如下图所示。

澳门新葡亰官网APP 5

①获取线程池中的职业线程的结果相比较难

1.7 实现撤销选项

在TAP方式中,完成撤销选项和以前的异步情势相仿,都以利用CancellationToken来贯彻,可是不一致的是Task构造函数允许传入八个CancellationToken,进而在义务实际运行在此之前撤销它。

亲自过问代码如下所示。

static void Main(string[] args)
{
    var cts = new CancellationTokenSource();
    // new Task时  可以传入一个 CancellationToken对象  可以在线程创建时  变取消任务
    var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token);
    WriteLine(longTask.Status);
    cts.Cancel();
    WriteLine(longTask.Status);
    WriteLine("第一个任务在运行前被取消.");

    // 同样的 可以通过CancellationToken对象 取消正在运行的任务
    cts = new CancellationTokenSource();
    longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token);
    longTask.Start();

    for (int i = 0; i < 5; i++)
    {
        Sleep(TimeSpan.FromSeconds(0.5));
        WriteLine(longTask.Status);
    }
    cts.Cancel();
    for (int i = 0; i < 5; i++)
    {
        Sleep(TimeSpan.FromSeconds(0.5));
        WriteLine(longTask.Status);
    }

    WriteLine($"这个任务已完成,结果为{longTask.Result}");

    ReadLine();
}

static int TaskMethod(string name, int seconds, CancellationToken token)
{
    WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}");

    for (int i = 0; i < seconds; i++)
    {
        Sleep(TimeSpan.FromSeconds(1));
        if (token.IsCancellationRequested)
        {
            return -1;
        }
    }

    return 42 * seconds;
}

运营结果如下图所示,这里须要静心的是,假若是在职分执行从前撤除了职务,那么它的终极状态是Canceled。假如是在推行进程中废除职务,那么它的图景是RanCompletion

澳门新葡亰官网APP 6

②完成线程池安徽中华南理哲高校程公司作线程推行的时序难题

1.8 管理职分中的格外

在职务中,处理特别和其余异步方式管理特别相符,若是能在所发生极度的线程中管理,那么毫无在其余地点管理。可是对于有个别不得预料的要命,那么能够由此二种方法来拍卖。

可以透过拜候task.Result属性来拍卖特别,因为访谈这么些天性的Get主意会使当前线程等待直到该职责成功,并将非常传播给当下线程,那样就能够经过try catch语句块来捕获分外。其它利用task.GetAwaiter().GetResult()措施和第使用task.Result雷同,相通能够捕获十分。假若是要捕获八个职务中的非凡错误,那么能够透过ContinueWith()艺术来拍卖。

切切实实哪些促成,演示代码如下所示。

static void Main(string[] args)
{
    Task<int> task;
    // 在主线程中调用 task.Result task中的异常信息会直接抛出到 主线程中
    try
    {
        task = Task.Run(() => TaskMethod("Task 1", 2));
        int result = task.Result;
        WriteLine($"结果为: {result}");
    }
    catch (Exception ex)
    {
        WriteLine($"异常被捕捉:{ex.Message}");
    }
    WriteLine("------------------------------------------------");
    WriteLine();

    // 同上 只是访问Result的方式不同
    try
    {
        task = Task.Run(() => TaskMethod("Task 2", 2));
        int result = task.GetAwaiter().GetResult();
        WriteLine($"结果为:{result}");
    }
    catch (Exception ex)
    {
        WriteLine($"异常被捕捉: {ex.Message}");
    }
    WriteLine("----------------------------------------------");
    WriteLine();

    var t1 = new Task<int>(() => TaskMethod("Task 3", 3));
    var t2 = new Task<int>(() => TaskMethod("Task 4", 4));

    var complexTask = Task.WhenAll(t1, t2);
    // 通过ContinueWith TaskContinuationOptions.OnlyOnFaulted的方式 如果task出现异常 那么才会执行该方法
    var exceptionHandler = complexTask.ContinueWith(t => {
        WriteLine($"异常被捕捉:{t.Exception.Message}");
        foreach (var ex in t.Exception.InnerExceptions)
        {
            WriteLine($"-------------------------- {ex.Message}");
        }
    },TaskContinuationOptions.OnlyOnFaulted);

    t1.Start();
    t2.Start();

    ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));
    // 人为抛出一个异常
    throw new Exception("Boom!");
    return 42 * seconds;
}

运作结果如下所示,须求注意的是,如若在ContinueWith()主意中捕获四个义务发生的百般,那么它的百般类型是AggregateException,具体的不行消息包括在InnerExceptions中间,要小心和InnerException区分。

澳门新葡亰官网APP 7

综上,大家在第3章中提过的异步编制程序模型和根据事件的异步编程模型,那几个形式使得获取结果更是便于,传播也更轻巧,但是在开展五个异步操作结合的时候,还须要编写制定大批量的代码。对于第叁个难题.NET 4.0提议了一个新的有关异步操作的API。叫做任务并行库(Task Parallel Library 简单称谓 TPL卡塔 尔(英语:State of Qatar)。

1.9 交互作用运转职责

本节中珍重介绍了多少个点子的利用,三个是等待组中全体任务都试行实现的Task.WhenAll()办法,另一个是少年老成旦组中三个主意实行完结都试行的Task.WhenAny()方法。

实际应用,如下演示代码所示。

static void Main(string[] args)
{
    // 第一种方式 通过Task.WhenAll 等待所有任务运行完成
    var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
    var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));

    // 当firstTask 和 secondTask 运行完成后 才执行 whenAllTask的ContinueWith
    var whenAllTask = Task.WhenAll(firstTask, secondTask);
    whenAllTask.ContinueWith(t => WriteLine($"第一个任务答案为{t.Result[0]},第二个任务答案为{t.Result[1]}"), TaskContinuationOptions.OnlyOnRanToCompletion);

    firstTask.Start();
    secondTask.Start();

    Sleep(TimeSpan.FromSeconds(4));

    // 使用WhenAny方法  只要列表中有一个任务完成 那么该方法就会取出那个完成的任务
    var tasks = new List<Task<int>>();
    for (int i = 0; i < 4; i++)
    {
        int counter = 1;
        var task = new Task<int>(() => TaskMethod($"Task {counter}",counter));
        tasks.Add(task);
        task.Start();
    }

    while (tasks.Count > 0)
    {
        var completedTask = Task.WhenAny(tasks).Result;
        tasks.Remove(completedTask);
        WriteLine($"一个任务已经完成,结果为 {completedTask.Result}");
    }

    ReadLine();
}

static int TaskMethod(string name, int seconds)
{
    WriteLine($"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}");

    Sleep(TimeSpan.FromSeconds(seconds));
    return 42 * seconds;
}

运维结果如下图所示。

澳门新葡亰官网APP 8

 

1.10 使用TaskScheduler配置职责实行

Task中,负权利务调节是TaskScheduler对象,FCL提供了多个派生自TaskScheduler的类型:线程池职务调治器(Thread Pool Task Scheduler)一齐上下文职责调整器(Synchronization Scheduler)。暗中认可情状下具备应用程序都使用线程池任务调治器,可是在UI组件中,不使用线程池中的线程,幸免跨线程更新UI,需求运用同步上下文任务调解器。能够因而实施TaskSchedulerFromCurrentSynchronizationContext()静态方法来获取对同步上下文职责调解器的援用。

演示程序如下所示,为了延时同盟上下文职务调解器,大家此番利用WPF来成立项目。

MainWindow.xaml 代码如下所示。

<Window x:Class="Recipe9.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Recipe9"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock Name="ContentTextBlock" HorizontalAlignment="Left" Margin="44,134,0,0" VerticalAlignment="Top" Width="425" Height="40"/>
        <Button Content="Sync" HorizontalAlignment="Left" Margin="45,190,0,0" VerticalAlignment="Top" Width="75" Click="ButtonSync_Click"/>
        <Button Content="Async" HorizontalAlignment="Left" Margin="165,190,0,0" VerticalAlignment="Top" Width="75" Click="ButtonAsync_Click"/>
        <Button Content="Async OK" HorizontalAlignment="Left" Margin="285,190,0,0" VerticalAlignment="Top" Width="75" Click="ButtonAsyncOK_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs 代码如下所示。

/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    // 同步执行 计算密集任务 导致UI线程阻塞
    private void ButtonSync_Click(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;

        try
        {
            string result = TaskMethod().Result;
            ContentTextBlock.Text = result;
        }
        catch (Exception ex)
        {
            ContentTextBlock.Text = ex.InnerException.Message;
        }
    }

    // 异步的方式来执行 计算密集任务 UI线程不会阻塞 但是 不能跨线程更新UI 所以会有异常
    private void ButtonAsync_Click(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;
        Mouse.OverrideCursor = Cursors.Wait;

        Task<string> task = TaskMethod();
        task.ContinueWith(t => {
            ContentTextBlock.Text = t.Exception.InnerException.Message;
            Mouse.OverrideCursor = null;
        }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
    }

    // 通过 异步 和 FromCurrentSynchronizationContext方法 创建了线程同步的上下文  没有跨线程更新UI 
    private void ButtonAsyncOK_Click(object sender, RoutedEventArgs e)
    {
        ContentTextBlock.Text = string.Empty;
        Mouse.OverrideCursor = Cursors.Wait;
        Task<string> task = TaskMethod(TaskScheduler.FromCurrentSynchronizationContext());

        task.ContinueWith(t => Mouse.OverrideCursor = null,
            CancellationToken.None,
            TaskContinuationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext());
    }

    Task<string> TaskMethod()
    {
        return TaskMethod(TaskScheduler.Default);
    }

    Task<string> TaskMethod(TaskScheduler scheduler)
    {
        Task delay = Task.Delay(TimeSpan.FromSeconds(5));

        return delay.ContinueWith(t =>
        {
            string str = $"任务运行在{CurrentThread.ManagedThreadId}上. 是否为线程池线程:{CurrentThread.IsThreadPoolThread}";

            Console.WriteLine(str);

            ContentTextBlock.Text = str;
            return str;
        }, scheduler);
    }
}

运行结果如下所示,从左至右依次单击按键,前四个开关将会吸引那一个。
澳门新葡亰官网APP 9

具体消息如下所示。

澳门新葡亰官网APP 10

TPL能够看成线程池之上的又二个抽象层,其对技士隐藏了与线程池人机联作的底层代码,并提供了更便于的细粒度的API。

参照书籍

正文主要参照了以下几本书,在这里对那几个我表示诚挚的感激,多谢您们为.Net的弘扬所做的孝敬!

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》
  5. 《C#三十二线程编程实战》

源码下载点击链接 演示源码下载

TPL的主导概念是职分。八个职分代表了八个异步操作,该操作能够运用各种措施运维,能够选择或不使用独立线程运营。

小编水平有限,若是不当应接各位舆情指正!

本来想趁待业时期的光阴读完《Multithreading with C# Cookbook Second Edition》那本书,而且享受做的有关笔记;然而出于作者近年来专门的职业规划和肉体原因,大概近年来都未曾时间来更新那么些种类,没办法完结几天大器晚成更。请我们多多满含!然则笔者必定将以此连串全体制改善进完结的!感激我们的支撑!

三个任务能够有多种措施和此外职务组合起来。比方,能够而且实行三个职责,等待全部职务到位,然后运行三个任务对前面全部的职责结果实行部分计量。TPL与事先的格局比较,个中叁个最首要优势是其具备用于组合职务的福利的API。

处理职分中的格外结果也是有四种办法。二个任务能够由三种任务组成,那些职务也得以有分别的子职务,所以有一个AggregateException的定义。这种特别能够捕获底层职务之中的有所极其,并同意单独管理那几个极度。

C#5.0中得以行使await和async关键词以平滑的,舒性格很顽强在荆棘载途或巨大压力面前不屈的方法开展操作任务。

 

4.2 创立职务

创办职分有三种办法:

1.向来开立义务实例,通超过实际例方法Start方法来运行任务

2.选取静态方法Task.Run和Task.Factory.StartNew来创制职责,两个都无需展现的调用start方法运维任务,差异在于前面一个是接班人的豆蔻年华种火速方式,前面一个可以动用附加的选项。

例:
1     class Program
2     {
3         static void Main(string[] args)
4         {
5             //第一种直接创建任务实例,需要用start方法来启动任务
6             var t1 = new Task(() => TaskMethod("Task 1"));
7             var t2 = new Task(() => TaskMethod("Task 2"));
8             t2.Start();
9             t1.Start();
10           //第二种通过Task.Factory.StartNew来创建任务
11           //这里Run方法只是Task.Factory.StartNew的一个快捷方式,Task.Factory.StartNew可以添加附加选项
12           Task.Run(() => TaskMethod("Task 3"));
13           Task.Factory.StartNew(() => TaskMethod("Task 4"));
14           //我们标记了该任务是长时间任务,结果该任务没有使用线程池,而是在单独的线程中运行
15           Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);
16           Thread.Sleep(TimeSpan.FromSeconds(1));
17         }
18 
19         static void TaskMethod(string name)
20         {
21             Console.WriteLine(
22                                 "Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
23                                  name,
24                                 Thread.CurrentThread.ManagedThreadId, 
25                                 Thread.CurrentThread.IsThreadPoolThread);
26         }
27   }

澳门新葡亰官网APP 11

※由于尚未对义务的时序做管理,所以屡屡进行每一趟都或然不形似。

※Task5接受的是独立线程的艺术来运作,可是依据运营该职分的日前的职务调节程序(task scheduler卡塔 尔(阿拉伯语:قطر‎,运维形式或然会区别。

 

4.3运用职分施行基本的操作

重视介绍怎么着从职务中收获结果。

1     class Program
2     {
3         static void Main(string[] args)
4         {
5              //启动主线程
6              TaskMethod("Main Thread Task");
7              //创建一个任务Task1,进行线程的同步
8              Task<int> task = CreateTask("Task 1");
9              task.Start();
10             //阻塞主线程,直到线程执行完成
11             int result = task.Result;
12             Console.WriteLine("Result is: {0}", result);
13 
14             //创建Taks2,使用RunSynchronously()方法进行同步
15             task = CreateTask("Task 2");
16             task.RunSynchronously();
17             result = task.Result;
18             Console.WriteLine("Result is: {0}", result);
19 
20             //创建Task3,此时不进行主线程的阻塞
21             task = CreateTask("Task 3");
22             Console.WriteLine(task.Status);
23             task.Start();
24 
25             //循环打印task的状态,直到任务完成
26             while (!task.IsCompleted)
27             {
28                 Console.WriteLine(task.Status);
29                 Thread.Sleep(TimeSpan.FromSeconds(0.5));
30             } 
31             
32             Console.WriteLine(task.Status);
33             result = task.Result;
34             Console.WriteLine("Result is: {0}", result);
35         }
36 
37         //创建一个新任务
38         static Task<int> CreateTask(string name)
39         {
40             return new Task<int>(() => TaskMethod(name));
41         }
42 
43         //任务需要处理的方法
44         static int TaskMethod(string name)
45         {
46             Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
47             name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
48             Thread.Sleep(TimeSpan.FromSeconds(2));
49             return 42;
50         }
51    }

奉行结果:

澳门新葡亰官网APP 12

 

4.4 组合职责

那边作者会学习到哪边将职务举行理并了结合,以致老爹和儿子职责之间的推行。废话不说,有码

实例1:

1     class Program
2     {
3         static void Main(string[] args)
4         {
5             //打印主线程
6             TaskMethod("Main Task", 1);
7             //创建两个任务
8             var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
9             var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));
10 
11             //设置firstTask的后续操作
12             firstTask.ContinueWith(
13                 t => Console.WriteLine("The first answer is {0}. Thread id {1}, is thread pool thread: {2}",
14                     t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
15                 TaskContinuationOptions.OnlyOnRanToCompletion);
16 
17              //启动两个任务
18             firstTask.Start();
19             secondTask.Start();
20             //延时4秒,足够两个任务完成的时间※↓这句是关键
21             Thread.Sleep(TimeSpan.FromSeconds(4));
22 
23             //为secondTask设置一个后续操作,TaskContinuationOptions.ExecuteSynchronously尝试同步方式执行后续操作
24             Task continuation = secondTask.ContinueWith(
25                 t => Console.WriteLine("The second answer is {0}. Thread id {1}, is thread pool thread: {2}",
26                     t.Result, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread),
27                 TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);
28 
29             //为之前的后续操作也定义一个后续操作,这里使用了C#5.0的方法GetAwaiter().OnCompleted()
30             continuation.GetAwaiter().OnCompleted(
31                 () => Console.WriteLine("Continuation Task Completed! Thread id {0}, is thread pool thread: {1}",
32                     Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread));
33 
34             Thread.Sleep(TimeSpan.FromSeconds(2));
35             Console.WriteLine();
36 
37             Thread.Sleep(TimeSpan.FromSeconds(10));
38         }
39 
40         static int TaskMethod(string name, int seconds)
41         {
42             Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
43                 name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
44             Thread.Sleep(TimeSpan.FromSeconds(seconds));
45             return 42 * seconds;
46         }
47  }

澳门新葡亰官网APP 13

这里大家见到secondTask的后续操作未有应用到线程池,为何吧?

阐述:由地点的代码大家看出,使用了TaskContinuationOptions.ExecuteSynchronously尝试同步格局实行后续操作,借使继续操作时间十分长暂,使用方面包车型客车艺术要命有功用的,因为放置在主线程进行运作要比放置在线程池中运作要快,那为什么会现出如此的情状吗,正是上边标志的延时代码的进献,这段延时期码使得SecondTask后续操作正好获得了方今职分施行的结果。未来作者把  Thread.Sleep(提姆eSpan.FromSeconds(4));注释掉再试一下,结果如下:

澳门新葡亰官网APP 14

认为到就疑似酒楼打饭,三人用餐,A帮B打饭。

首先种是:A打完饭后,发现B刚来,就一贯把饭给了B,然后B直接吃了

其次种是:A打饭的时候,B正好也来了,于是两个人一块站队,A打完饭后再把饭给了B

 

例2:演示了弹指间父亲和儿子任务之间的涉嫌。

1 class Program
2     {
3         static void Main(string[] args)
4         {
5              //创建一个父任务
6              var firstTask = new Task<int>(() =>
7             {
8                 //创建一个子任务,使用TaskCreationOptions.AttachedToParent来标识
9                 var innerTask = Task.Factory.StartNew(
10                                         () => TaskMethod("Second Task", 5), 
11                                         TaskCreationOptions.AttachedToParent);
12               //创建一个子任务的后续操作,该后续操作也会影响父任务
13                innerTask.ContinueWith(
14                                         t => TaskMethod("Third Task", 2), 
15                                         TaskContinuationOptions.AttachedToParent);
16                 return TaskMethod("First Task", 2);
17             });
18 
19             //启动任务
20             firstTask.Start();
21 
22             //循环打印任务的状态
23             while (!firstTask.IsCompleted)
24             {
25                 Console.WriteLine(firstTask.Status);
26                 Thread.Sleep(TimeSpan.FromSeconds(0.5));
27             }
28             Console.WriteLine(firstTask.Status);
29 
30             Thread.Sleep(TimeSpan.FromSeconds(10));
31         }
32 
33         static int TaskMethod(string name, int seconds)
34         {
35             Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
36                 name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
37             Thread.Sleep(TimeSpan.FromSeconds(seconds));
38             return 42 * seconds;
39         }

澳门新葡亰官网APP 15

地点结果突显,父职分必需等待全数的子职务成功工夫达成,不过看不出来他们是生龙活虎道照旧异步试行的。因为从First Task和Sencod Task它们中间的周转时序上也看不出来他们是阿爸奉行完了再实行的子职责,所以笔者感觉把父职责的时刻调长一点,那回小编让父任务实施10s

修改:

   return TaskMethod("First Task", 2);  →   return TaskMethod("First Task", 10);

结果如下

澳门新葡亰官网APP 16

那回显得的都是firstTask的Running状态,所以理应能一定父亲和儿子之间暗中同意意况下也是异步实践的。因为父职必需定要等子职分全甘休本领一鼓作气。

 

 

本文由澳门新葡亰app发布于澳门新葡亰,转载请注明出处:澳门新葡亰官网APP2.EAP(基于事件的异步编程模式

关键词: