2023年4月

Linux操作系统的网络模块是负责网络通信的核心部分。它通过实现各种协议和算法,使得计算机能够在网络中进行数据交换和通信。网络模块主要包括以下几个方面的功能:

(1)IP协议栈:负责处理网络层的数据包,实现IP地址的分配、路由选择等功能。

IP协议栈是网络模块中最基本的部分,它负责处理网络层的数据包。在Linux操作系统中,IP协议栈主要由IP协议和路由协议组成。IP协议是一种无连接的协议,它主要负责将数据包从源地址传输到目的地址。路由协议则负责选择最佳的路径,使数据包能够快速地到达目的地。

(2)TCP/IP协议栈:负责处理传输层的数据包,实现数据的可靠传输、流量控制等功能。

TCP/IP协议栈是网络模块中的另一个重要部分,它负责处理传输层的数据包。在Linux操作系统中,TCP/IP协议栈主要由TCP协议和UDP协议组成。TCP协议是一种面向连接的协议,它能够保证数据的可靠传输。UDP协议则是一种无连接的协议,它主要用于实现高速传输和广播等功能。

(3)Socket接口:提供应用程序与网络模块之间的接口,使得应用程序能够通过网络与其他计算机进行通信。

Socket接口是网络模块中的另一个重要部分,它提供了应用程序与网络模块之间的接口。通过Socket接口,应用程序可以与其他计算机进行通信,实现数据的传输和交换。

  1. 网络模块的实现原理

Linux操作系统的网络模块是基于内核实现的。它通过内核中的网络协议栈来实现网络通信。网络协议栈是指一组协议的集合,包括网络层、传输层和应用层。Linux操作系统中的网络协议栈主要由以下几个部分组成:

(1)网络层:网络层是协议栈的底层,负责处理IP数据包的转发和路由选择等功能。在Linux操作系统中,网络层主要由IP协议和路由协议组成。

Linux操作系统的网络层主要由IP协议和路由协议组成。IP协议是一种无连接的协议,它主要负责将数据包从源地址传输到目的地址。路由协议则负责选择最佳的路径,使数据包能够快速地到达目的地。常见的路由协议包括RIP、OSPF和BGP等。

(2)传输层:传输层主要负责处理TCP和UDP等传输协议。在Linux操作系统中,传输层主要由TCP/IP协议栈实现。

Linux操作系统的传输层主要由TCP/IP协议栈实现。TCP协议是一种面向连接的协议,它能够保证数据的可靠传输。UDP协议则是一种无连接的协议,它主要用于实现高速传输和广播等功能。在Linux操作系统中,TCP/IP协议栈还包括了一些优化技术,如Nagle算法、延迟确认等。

(3)应用层:应用层是协议栈的最上层,主要负责处理应用程序和网络之间的数据传输。在Linux操作系统中,应用层主要由Socket接口实现。

Linux操作系统的应用层主要由Socket接口实现。Socket接口提供了应用程序与网络模块之间的接口,使得应用程序能够通过网络与其他计算机进行通信。在Linux操作系统中,Socket接口还包括了一些高级协议,如HTTP、FTP和SMTP等。

  1. 网络模块的优化技术

为了提高网络模块的性能和效率,Linux操作系统采用了多种优化技术。其中,最常见的优化技术包括以下几个方面:

(1)零拷贝技术:零拷贝技术是一种优化网络IO性能的技术。它通过减少数据拷贝的次数,提高了网络IO的效率。

零拷贝技术是一种优化网络IO性能的技术。它通过减少数据拷贝的次数,提高了网络IO的效率。在Linux操作系统中,零拷贝技术主要通过mmap和sendfile等系统调用实现。

(2)TCP/IP协议栈优化:Linux操作系统中的TCP/IP协议栈采用了多种优化技术,如Nagle算法、延迟确认等,以提高网络传输效率。

Linux操作系统中的TCP/IP协议栈采用了多种优化技术,如Nagle算法、延迟确认等,以提高网络传输效率。Nagle算法能够减少网络拥塞,提高网络传输效率。延迟确认则能够减少网络延迟,提高网络传输速度。

(3)中断处理优化:Linux操作系统采用了中断处理优化技术,如中断共享、中断处理线程等,以提高网络模块的性能和效率。

Linux操作系统采用了中断处理优化技术,如中断共享、中断处理线程等,以提高网络模块的性能和效率。中断共享能够减少中断的数量,提高系统的稳定性和可靠性。中断处理线程则能够提高中断处理的效率,减少系统的开销。

(4)网卡绑定技术:网卡绑定技术是一种将多个网卡绑定在一起,形成一个虚拟网卡的技术。它可以提高网络带宽和可靠性。

网卡绑定技术是一种将多个网卡绑定在一起,形成一个虚拟网卡的技术。它可以提高网络带宽和可靠性。在Linux操作系统中,网卡绑定技术主要通过bonding驱动实现。

  1. 总结

Linux操作系统的网络模块是其重要的组成部分之一。它通过实现各种协议和算法,使得计算机能够在网络中进行数据交换和通信。本文从网络模块的作用、实现原理和优化技术三个方面对Linux操作系统的网络模块进行了详细的介绍。Linux操作系统的网络模块是一个复杂的系统,需要不断地进行优化和改进,以满足日益增长的网络需求。

CHINESE OPEN INSTRUCTION GENERALIST: A PRELIMINARY RELEASE

论文:https://arxiv.org/pdf/2304.07987v1.pdf

数据地址:https://huggingface.co/datasets/BAAI/COIG

Part1

介绍

COIG的特点:

  • 领域适应性 :如表1所示。我们考虑了指令微调数据集的四个方面(验证,格式,文化,缩放)。对于每个领域,我们调整我们的数据收集管道,以更好地反映该领域的特点。


验证:反应是否可以被验证。
格式:格式是否至关重要。
文化:反应是否取决于某种文化。
尺度:尺度是否重要。

  • 多样性 :我们考虑了各种任务,包括常识推理、人类价值排列、代码生成和幻觉纠正,而很少有中 文指令微调数据是特意为这样一个完整的光谱而设计的。
  • 由人类进行质量检查 :与现有的模型生成的中文教学语料库相比,包括(Ziang Leng and Li, 2023; LianjiaTech, 2021; Xue et al., 2023; JosephusCheung, 2021),COIG的翻译语料库由人类注释者仔细验证。此外,由于COIG翻译语料库是从具有不同任务的英语教学语料库(Wang等人,2022b; Honovich等人,2022;Wang等人,2022a)翻译而来,它比在现有的中文数据集上通过适应提示工程建立的中文教学语料库更加多样化,例如(Zeng等人,2023;杨,2023;郭等人,2023)。

COIG数据的主要部分是已经存在于网络上的实际数据,我们根据它们的特点将其转换为合适的指令遵循方式。例如,在学术考试领域,我们抓取了中国高考、公务员考试等63.5千条指令并进行了人工注释。COIG的特点还包括华语世界中人类价值取向的数据,以及基于leetcode的编程指令的样本。为了保证最终的数据质量,我们聘请了223名中国大学生作为质量检查员,帮助我们进行数据过滤、修正和评级。由此产生的COIG语料库是一个全面的集合,可以使中国的法律硕士在许多领域具有很强的指令跟随能力。COIG语料库可以在huggingface和github找到并将持续更新。

此外,我们根据经验观察提供了对数据构建管道的见解。我们证明了为不同的领域选择合适的管道是至关重
要的,并且我们提出了在COIG所涵盖的领域中构建指令调整数据的最佳实践(第3节),这可以作为未来指
令语料库构建工作流程设计的参考。

该文的贡献如下:

  • 据我们所知,这是最早的研究工作之一,专门总结了现有的中文指令微调语料库,并就未来如何构建中文指令微调语料库提出了见解。
  • 我们构建了5个开源的高质量中文指令语料库,包括68k的普通中文指令语料库、62k的中文考试指令语料库、3k的中文人值对齐语料库和13k的中文反事实校正多轮聊天语料库,作为沿着指出的研究方向构建新的中文教学语料库的样本。
  • 我们构建了一个人工验证的通用高质量中文指令调优语料库,可直接用于中文LLMs的指令调优,包括商业和非商业的。

Part2

现有的指令语料库

如果指令数据是从现有的公共数据集中获得的,并且数据处理管道是公开的,那么它就被认为是开源的。

获取数据集的一般手段有:人工标注、半自动和自动构建、使用LLM、翻译。

Part3

COIG:中文开源指令数据通用语料库

第3.1节中分别介绍了一个经过人工验证的普通指令语料库,在第3.2节中介绍了一个经过人工注释的考试指令语料库,在第3.3节中介绍了一个人类价值调整指令语料库,在第3.3节中介绍了一个多轮反事实修正聊天语料库,在第3.5节中介绍了一个leetcode指令语料库。我们提供这些新的指令语料库是为了帮助社区对中文LLMs进行指令调整。这些指令语料库也是如何有效建立和扩展新的中文指令语料库的模板工作流程。

1
基于翻译的通用指令语料库

为了减少成本并进一步提高指令语料库的质量,我们将翻译程序分为三个阶段:
自动翻译、人工验证和人
工纠正


自动翻译
:将指令输入和输出进行拼接输入到DeepL进行翻译。

人工验证
:定义四个标签:1)可直接使用;2)可以使用,但要有实例的源输入和输出;3)需要人工修正后使用;4)不可使用。不可用的情况非常少,不到20个。我们在人工验证阶段采用了两阶段的质量验证:在第一阶段,每个案例在经过注释者的注释后,由一位具有5年以上工作经验的工业界有经验的质量检查员进行核查。当且仅当正确率超过95%时,整个语料库才能进入第二个质量验证阶段。最终,该语料库在第一个质量验证阶段得到了96.63%的正确率。我们的专家质量检查员(即我们的合作者)负责第二个质量验证阶段,只从总语料库中随机抽取200个案例进行质量验证。如果并且只有在所有抽样的案例都被正确分类的情况下,语料库才能够进入人工修正阶段。

人工纠正
:在人工修正阶段,要求注释者将翻译的指令和实例修正为正确的中文{指令、输入、输出}三要素,而不是仅仅保持翻译的正确性。要求注释者这样做是因为在源的非自然指令中存在事实错误,可能导致LLMs的幻觉。总共有18074条指令被送入人工纠正阶段。我们使用与人工验证阶段相同的两阶段质量验证程序。在人工修正阶段的第一个质量验证阶段,语料库得到了97.24%的正确率。这些严格的质量验证程序保证了翻译语料库的可靠性。

2
考试指令语料库

我们使用
potato(Pei et al., 2022)
,一个主动学习驱动的开源注解网站模板,进行人工注解,从原始考试题中提取六个信息元素,包括指令、问题背景、问题、答案、答案分析和粗粒度的主题。这些考试中有很多阅读理解题,问题背景指的是这些阅读理解题的阅读材料。有六个主要的粗略科目:中文、英文、政治、生物、历史和地质。语料库中很少有数学、物理和化学问题,因为这些问题往往带有复杂的符号,很难进行注释。我们说明了问题格式百分比,说明了主要科目百分比。对于许多选择题,我们建议研究人员利用这个语料库,使用提示语对其进行进一步的后处理,或将其后处理为填空题,以进一步增加指令的多样性。

3
人类价值对齐指令语料库

我们将价值排列数据分为两个独立的集合:1)一组呈现华语世界共同的人类价值的样本;2)一些呈现区域文化或国家特定的人类价值的额外样本集合。对于第一套共享的样本,我们选择了
self-instruct( Wang et al., 2022a)
作为主要的方法来增加一套种子指令遵循的样本。对于附加集,为了保证数据真实地反 映当地的价值观,我们主要依靠
网络爬虫
来收集原始形式的数据。

人类共同价值观的种子指令是人工从中国道德教育的教科书和考试中挑选出来的,因为我们相信这些材料中的大部分内容已经考虑了不同群体的共同点(例如,中国有56个少数民族)。在过滤数据时,我们特意考虑了以下三个原则:

  • 它应该介绍在华语世界被广泛接受的人类共同价值观,而不是区域性的。
  • 它不应该包括政治宣传或宗教信仰,也不应该与有争议的索赔有关。
  • 它不应该只是解释谚语或名言,因为它们很可能会在知识检索说明-后续数据中涉及。

我们总共选择了50条指令作为扩增种子,并产生了3k条产生的指令,跟随样本用于华语世界的通用价值对齐。同时,我们还收集了19,470个样本作为区域性的增补,这些样本是针对中国用户的(包括许多只在中文社区使用的术语)。

4
多轮反事实修正聊天语料库

我们构建了反事实修正多轮聊天数据集(CCMC)。它是基于
CN-DBpedia知识库(Xu等人,2017)
构建的,目的是缓解和解决当前LLM中的幻觉和事实不一致的痛点。原始知识库由5634k个实体及其对应的属性-价值对和原始文本组成。

CCMC数据集包括一个学生和一个老师之间的5轮角色扮演聊天,以及他们参考的相应知识。老师根据基础知识产生回答,并在每一轮中纠正学生的问题或陈述中的事实错误或不一致之处。在最后一轮中,老师会总结聊天内容,并审查混乱的术语,即学生的问题或陈述中的事实错误或不一致之处。该数据集包含13,653个对话,导致68,265轮的聊天。

我们概述了创建CCMC数据集的工作流程。该工作流程包括三个主要部分:
实体选择、信息提取和聊天生成

实体选择
:根据
实体标签频率
对实体进行排名,并选择前200名。我们优先考虑有摘要的实体
,并旨在保留基于事实/知识的内容,如成熟的、经过历史检验的概念,以及与各种学术学科、历史事件和
社会事件相关的实体。像组织、公司、食品和游戏这样的标签被排除在外。

信息提取
:使用一个聊天的LLM从知识库中提取信息。我们首先通过从高优先级的类别中随机抽出一个实体来获得源实体,返回三联体、内容摘要和内容部分的标题。然后我们要求聊天LLM将所有的信息总结成一个更好的摘要,同时从输入中提取属性-价值对。这可以过滤掉百度标签中的一些虚假标签,也可以把非结构化内容中的信息考虑进去。对于混淆实体,我们使用基于提示的方法来提取基于输入信息的混淆术语列表。然后我们将这些术语与知识库进行匹配。如果该术语存在于知识库中,我们就保留该术语,并使用同样的方法来提取更好的摘要和属性值对。

生成聊天记录
:采用师生问答的方式来生成聊天记录,逐步生成攻击和防御场景。我们提供提取的原始实体摘要和混淆的实体摘要。然后,我们让学生向老师询问原始概念,同时将其与混乱的概念错误地混为一谈。然后,老师会以JSON格式来澄清和区分这些概念。对话将持续多轮,每次都是学生根据之前的对话来挑战老师,而老师则提供澄清和区别。在最后一轮,老师会重新介绍原来的概念,并总结容易混淆的概念,强调和区分学生之前混淆的概念。所有的聊天都是通过提示聊天LLM产生的。

5
leetcode语料库

鉴于
代码相关的任务可能有助于LLMs的能力出现(Ouyang等人,2022)
,我们认为在我们的数据集中应该考虑与中文自然语言一致的代码相关任务。因此,我们从CC-BY-SA-4.0许可的集合中建立了Leetcode指令的2,589个编程问题。这些问题包含问题描述、多种编程语言和解释.

考虑到输入和输出,我们将指令任务分为两类:代码到文本和文本到代码。代码到文本的任务要求产生给定的编程代码的功能描述,而文本到代码的任务则要求从问题中输出代码。根据程序问题是否有相应的解释,任务指令将被区分为有/无解释。我们准备了38种类型的说明来生成Leetcode指令。我们对每个程序问题的可用编程语言实现进行迭代,随机抽取任务为代码到文本或文本到代码,然后随机选择一个相应的指令描述。

6
指令语料库构建工作流程的实证验证

本节总结了关于中文指令语料库建设工作流程的合理实证结论和经验。

  • 首先,当我们想扩大指令语料库的规模时,采用
    语境学习
    (ICL)来生成新的指令(Wang等人,2022a; Honovich等人,2022)是一个关键的促进因素。以表1中的通用指令语料库(LianjiaTech, 2021; Taori et al.1 为例,利用现有LLM的ICL能力而不是依靠人工注释或其他方法来生成这些指令是比较现实的。LLM的开发者应该根据源头的许可、源头与OpenAI的关系以及他们的需求,仔细决定他们喜欢哪种LLM和种子指令体的关系,以及他们的需求。

  • 其次,当目标语言和源指令语料库的语言之间存在文化差异时,需要进行
    人工注释或验证
    。正如第3.3节所述,我们必须仔细选择人工指令中的种子,以确保种子指令与中国文化保持良好的一致性,不包括政治宣传或区域信仰。我们还建议使用现有的语料库,比如在构建人类价值对齐指令时,使用(Ethayarajh等人,2023)中介绍的方法,即从论坛中抓取语料,并对其进行后处理,使其无害化。

  • 第三,模型生成的语料需要
    更详细的人工质量验证
    ,特别是在输出格式至关重要的情况下。在第3.1节中解释的非自然指令(Honovich等人,2022)的翻译和验证过程中,我们注意到许多不遵循模型生成的指令的实例,以及相当数量的不完善的模型生成的指令。另一个问题是,模型生成的指令的多样性和分布高度依赖于种子指令。人工选择和验证可能有助于从大型原始指令语料库中抽取指令语料,其分布比大型原始指令语料库本身更均衡,多样性更好,正如(Geng等人,2023)所指出的。

.NET 在数年前就在语言和库中添加了 async/await。在那段时间里,它像野火一样蔓延开来,不仅在 .NET 生态系统中,而且在无数其他语言和框架中被复制。在 .NET 中也看到了大量的改进,包括利用异步的额外语言构造、提供异步支持的 API 以及在基础设施中实现 async/await (特别是在 .NET Core 中性能和诊断支持方面的改进)。

然而,async/await 中的 ConfigureAwait,引发了一些疑问。在这篇文章中,我希望能回答其中的许多问题。我希望这篇文章从头到尾都是易读的,同时也是一个常见问题的列表,可以作为将来的参考。

要真正理解  ConfigureAwait,我们需从更基础的一些东西说起。

SynchronizationContext 是什么?

SynchronizationContext 在 MSDN 中描述为:“提供了在各种同步模型中传播同步上下文的基本功能。”

对于99.9%的用例,SynchronizationContext 只是一个类型,它提供了一个 virtual Post 方法,提供了一个异步执行的委托。(SynchronizationContext 有各种各样的其他虚拟成员,但他们很少使用本文不做讨论)。基类型的 Post 实际上只是调用 ThreadPool.QueueUserWorkItem 异步调用提供的委托。但是,派生类型会重写 Post 以使该委托能够在最合适的地方和最合适的时间执行。

例如,Windows Form 有一个 WindowsFormsSynchronizationContext 派生类型,它重写了 Post 方法,内部其实就是 Control.BeginInvoke。

这意味着,对其 Post 方法的任何调用都将导致该委托在与相关控件关联的线程(又名“UI线程”)上稍后的某个点被调用。Windows Forms 依赖于 Win32 消息处理,并在 UI 线程上运行一个“消息循环”,该线程只是坐着等待新消息到达处理。这些消息可以用于鼠标移动和单击、键盘输入、系统事件、可调用的委托等。因此,给定 Windows Form 应用程序 UI 线程的 SynchronizationContext 实例,要获得一个在那个 UI 线程上执行的委托,只需要将它传递给 Post 即可。

WPF 也是如此。DispatcherSynchronizationContext 派生类型的 Post 内部其实是 Dispatcher.BeginInvoke,在这种情况下由一个 WPF Dispatcher 管理而不是一个 Windows Forms Control。

WinRT 也类似。它有自己的 SynchronizationContext 派生类型,带有一个 Post 重载,该重载通过 CoreDispatcher 将委托排队到 UI 线程。

这不仅仅是“在 UI 线程上运行这个委托”。任何人都可以实现 SynchronizationContext 派生类来做任何事情。例如,我可能不关心委托在哪个线程上运行,但我想确保任何 Post 到我的 SynchronizationContext 的委托都是在有限的并发程度下执行的。我可以通过一个自定义的 SynchronizationContext 来实现:

internal sealed classMaxConcurrencySynchronizationContext : SynchronizationContext
{
private readonlySemaphoreSlim _semaphore;public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel) =>_semaphore= newSemaphoreSlim(maxConcurrencyLevel);public override void Post(SendOrPostCallback d, object state) =>_semaphore.WaitAsync().ContinueWith(delegate{try { d(state); } finally{ _semaphore.Release(); }
},
default, TaskContinuationOptions.None, TaskScheduler.Default);public override void Send(SendOrPostCallback d, objectstate)
{
_semaphore.Wait();
try { d(state); } finally{ _semaphore.Release(); }
}
}

实际上,xunit 提供了一个与此非常类似的 SynchronizationContext,它使用它来限制并发运行的测试相关联的代码数量。

namespaceXunit.Sdk
{
/// <summary> ///An implementation of<see cref="SynchronizationContext"/>which runs work on custom threads///rather than in the thread pool, and limits the number of in-flight actions./// </summary> public classMaxConcurrencySyncContext : SynchronizationContext, IDisposable
{
bool disposed = false;readonly ManualResetEvent terminate = new ManualResetEvent(false);readonly List<XunitWorkerThread>workerThreads;readonly ConcurrentQueue<Tuple<SendOrPostCallback, object, object>> workQueue = new ConcurrentQueue<Tuple<SendOrPostCallback, object, object>>();readonly AutoResetEvent workReady = new AutoResetEvent(false);/// <summary> ///Initializes a new instance of the<see cref="MaxConcurrencySyncContext"/>class./// </summary> /// <param name="maximumConcurrencyLevel">The maximum number of tasks to run at any one time.</param> public MaxConcurrencySyncContext(intmaximumConcurrencyLevel)
{
workerThreads
= Enumerable.Range(0, maximumConcurrencyLevel)
.Select(_
=> newXunitWorkerThread(WorkerThreadProc))
.ToList();
}
/// <summary> ///Gets a flag indicating whether maximum concurrency is supported./// </summary> public static boolIsSupported=>ExecutionContextHelper.IsSupported;/// <inheritdoc/> public voidDispose()
{
if(disposed)return;

disposed
= true;
terminate.Set();
foreach (var workerThread inworkerThreads)
{
workerThread.Join();
workerThread.Dispose();
}

terminate.Dispose();
workReady.Dispose();
}
/// <inheritdoc/> public override void Post(SendOrPostCallback d, objectstate)
{
//HACK: DNX on Unix seems to be calling this after it's disposed. In that case,//we'll just execute the code directly, which is a violation of the contract//but should be safe in this situation. if(disposed)
Send(d, state);
else{var context =ExecutionContextHelper.Capture();
workQueue.Enqueue(Tuple.Create(d, state, context));
workReady.Set();
}
}
/// <inheritdoc/> public override void Send(SendOrPostCallback d, objectstate)
{
d(state);
}

[SecuritySafeCritical]
voidWorkerThreadProc()
{
while (true)
{
if (WaitHandle.WaitAny(new WaitHandle[] { workReady, terminate }) == 1)return;

Tuple
<SendOrPostCallback, object, object>work;while (workQueue.TryDequeue(outwork))
{
//Set workReady() to wake up other threads, since there might still be work on the queue (fixes #877) workReady.Set();if (work.Item3 == null) //Fix for #461, so we don't try to run on a null execution context RunOnSyncContext(work.Item1, work.Item2);elseExecutionContextHelper.Run(work.Item3, _=>RunOnSyncContext(work.Item1, work.Item2));
}
}
}

[SecuritySafeCritical]
void RunOnSyncContext(SendOrPostCallback callback, objectstate)
{
var oldSyncContext =Current;
SetSynchronizationContext(
this);
callback(state);
SetSynchronizationContext(oldSyncContext);
}
}
}

这些派生类都比较类似:SynchronizationContext 提供了一个单独的 API,可用于对委托进行排队,以便按用户需求进行处理,而不需要知道实现的细节。所以,如果我正在写一个库,我想开始做一些工作,然后将一个委托排队回到原始位置的“context”,我只需要抓住他们的 SynchronizationContext,持有它,然后当我完成工作后,在那个context上调用Post来传递我想要调用的委托。我不需要知道,对于 Windows 窗体,我应该获取一个 Control 并使用它的 BeginInvoke,或者对于 WPF,我应该获取一个 Dispatcher 并使用它的 BeginInvoke,或者对于 xunit,我应该以某种方式获取它的上下文并排队到它;我只需要获取当前的 SynchronizationContext 并在后面使用它。为了实现这一点,SynchronizationContext 提供了一个 Current  属性,这样,为了实现前面提到的目标,我可能会写这样的代码:

public voidDoWork(Action worker, Action completion)
{
SynchronizationContext sc
=SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_
=>{try{ worker(); }finally { sc.Post(_ => completion(), null); }
});
}

框架使用 SynchronizationContext.SetSynchronizationContext 方法,通过 Current 暴露一个自定义上下文。

TaskScheduler 是什么?

SynchronizationContext 是“scheduler”的一般抽象。个别框架有时对调度程序有自己的抽象,System.Threading.Tasks 也不例外。当 Task 由委托支持,以便它们可以排队并执行时,它们与 System.Threading.Tasks.TaskScheduler 相关联。正如 SynchronizationContext 提供了一个 virtual 的 Post 方法来对委托的调用进行排队(实现稍后会通过标准的委托调用机制来调用委托),TaskScheduler 提供了一个 abstract 的 QueueTask 方法(实现稍后通过 ExecuteTask 方法调用该任务)。

调度器通过 TaskScheduler.Default 返回的默认调度器是线程池,但是可以从 TaskScheduler 派生并覆盖相关的方法,从而实现何时何地调用 Task 的行为。例如,核心库包括 System.Threading.Tasks.ConcurrentExclusiveSchedulerPair 类型。这个类的实例暴露了两个 TaskScheduler 属性,一个称为 ExclusiveScheduler,另一个称为 ConcurrentScheduler。任务调度到 ConcurrentScheduler 的中运行,则可以同时运行,但有并发限制,受制于 ConcurrentExclusiveSchedulerPair。(类似于前面说的 MaxConcurrencySynchronizationContext)。当 Task 被调度到 ExclusiveScheduler 中运行时,没有任何 ConcurrentScheduler 的 Task 会运行,一次只允许运行一个排他性 Task……在这种情况下,它的行为非常类似于一个 reader/writer-lock。

与 SynchronizationContext 一样,TaskScheduler 也有一个 Current 属性,它返回当前的 TaskScheduler。然而,与 SynchronizationContext 不同的是,它没有设置当前调度器的方法。作为替代,当前调度器是与当前运行的 Task 相关联的调度器,并且作为启动 Task 的一部分提供给系统的调度器。下面程序将输出“True”,因为与 StartNew 一起使用的 lambda 在 ConcurrentExclusiveSchedulerPair 的 ExclusiveScheduler 上执行,查看设置到该调度程序的 TaskScheduler.Current:

usingSystem;usingSystem.Threading.Tasks;classProgram
{
static voidMain()
{
var cesp = newConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(()
=>{
Console.WriteLine(TaskScheduler.Current
==cesp.ExclusiveScheduler);
},
default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}

有趣的是,TaskScheduler 提供了一个静态的 FromCurrentSynchronizationContext 方法,该方法创建一个新的 TaskScheduler,使用它的 Post 方法对任务进行排队,以便在 SynchronizationContext.Current 返回的上下文上运行。

SynchronizationContext、TaskScheduler 与 await 的关联?

考虑编写一个带有按钮的 UI 应用程序。单击按钮时,我们希望从 web 站点下载一些文本,并将其设置为按钮的内容。该按钮只能从拥有它的 UI 线程访问,因此当我们成功下载了新的文本并希望将其存储回按钮的内容时,我们需要从拥有控件的线程进行操作。如果我们不这样做,我们会得到一个异常:

System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

如果我们手动实现,我们可以使用 SynchronizationContext,如前面所示,将内容设置回原始上下文,例如可以通过 TaskScheduler:

private static readonly HttpClient s_httpClient = newHttpClient();private void downloadBtn_Click(objectsender, RoutedEventArgs e)
{
s_httpClient.GetStringAsync(
"http://example.com/currenttime").ContinueWith(downloadTask =>{
downloadBtn.Content
=downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}

或者直接使用 SynchronizationContext:

private static readonly HttpClient s_httpClient = newHttpClient();private void downloadBtn_Click(objectsender, RoutedEventArgs e)
{
SynchronizationContext sc
=SynchronizationContext.Current;
s_httpClient.GetStringAsync(
"http://example.com/currenttime").ContinueWith(downloadTask =>{
sc.Post(
delegate{
downloadBtn.Content
=downloadTask.Result;
},
null);
});
}

但是,这两种方法都显式地使用回调,下面使用 async/await 实现:

private static readonly HttpClient s_httpClient = newHttpClient();private async void downloadBtn_Click(objectsender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content
=text;
}

这“刚刚好”,成功地在 UI 线程上设置了 Content,因为就像上面手动实现的版本一样,await 一个 Task 在默认情况下会注意到 SynchronizationContext.Current 和 TaskScheduler.Current。当你在 C# 中 await 任何东西时,编译器会通过调用 GetAwaiter 将代码转换为“awaitable”类型,在本例中是 Task,所以转换为“awaiter”,在本例中是 TaskAwaiter。awaiter负责连接回调(通常称为“continuation”),当等待的对象完成时它将回调到状态机中,它使用在注册回调时捕获的 context/scheduler 来执行此操作。

虽然不是完全使用的代码(有额外的优化和调整),它是这样的:

object scheduler =SynchronizationContext.Current;if (scheduler is null && TaskScheduler.Current !=TaskScheduler.Default)
{
scheduler
=TaskScheduler.Current;
}

换句话说,它首先检查是否有 SynchronizationContext 集,如果没有,是否有一个非默认的 TaskScheduler 在运行。如果它找到一个,当回调准备被调用时,它将使用捕获调度程序;否则,它通常只执行回调,作为完成等待的任务的操作的一部分。

ConfigureAwait(false) 做了什么?

ConfigureAwait 方法并不特殊:编译器或运行时都不会以任何特殊的方式识别它。它只是一个简单方法,返回一个 struct(ConfiguredTaskAwaitable),该 struct 包装了调用它的原始任务,就像 Boolean 一样被调用。请记住,await 可以用于任何暴露正确模式的类型。通过返回一个不同的类型,这意味着当编译器访问实例 GetAwaiter 方法(模式的一部分),它是在 ConfigureAwait 返回的类型之外执行的,而不是直接在任务之外执行,并提供了一个钩子,通过这个定制的 awaiter 来改变等待的行为。

具体来说,等待从 ConfigureAwait(continueOnCapturedContext: false) 返回的类型,而不是直接等待 Task ,最终会影响前面显示的如何捕获目标 context/scheduler 的逻辑。它有效地使前面展示的逻辑更像这样:

object scheduler = null;if(continueOnCapturedContext)
{
scheduler
=SynchronizationContext.Current;if (scheduler is null && TaskScheduler.Current !=TaskScheduler.Default)
{
scheduler
=TaskScheduler.Current;
}
}

换句话说,通过指定 false,即使存在要回调的当前上下文或调度程序,也会假装没有上下文或调度程序。

我为什么要使用 ConfigureAwait(false)?

ConfigureAwait(continueOnCapturedContext: false) 用于避免在原始上下文或调度器上强制调用回调。这有几个好处:

性能改进

如果不是调用它而是要排队进行回调,就会产生更高的成本,因为有额外的工作(通常是额外的分配),也因为这意味着某些优化我们在运行时不能使用(我们可以做更多的优化,当我们知道如何调用回调,但是如果是交给任意一个抽象的实现,我们能做的就很有限了)。对于非常热的路径,甚至检查当前 SynchronizationContext 和当前 TaskScheduler(两者都涉及线程的静态访问)的额外成本也会增加可测量的开销。如果 await 后的代码实际上并不需要运行在原来的上下文下,使用 ConfigureAwait(false) 可以避免所有这些成本:它不需要不必要的排队,它可以利用所有的优化,而且可以避免不必要的线程静态访问。

避免死锁

考虑一种使用 await 某个网络下载结果的库方法。您可以调用这个方法并同步地阻塞它,等待它完成,比如在返回的 Task 对象上使用 .Wait() 或 .Result或.GetAwaiter().GetResult()。现在考虑如果你调用它时当前 SynchronizationContext 是限制数量的操作可以运行数量为1,不管是明确地通过类似 MaxConcurrencySynchronizationContext 设置,还是隐式地使用只有一个线程可以被使用上下文,例如一个UI线程。因此,调用该线程上的方法,然后阻塞它,等待操作完成。该操作启动了网络下载并等待它。因为在默认情况下,等待 Task 将捕获当前的 SynchronizationContext,所以它就这样做了,当网络下载完成时,它会返回到 SynchronizationContext,这个回调将调用剩余的操作。但是唯一能够处理队列回调的线程目前被阻塞等待操作完成的代码阻塞。直到回调处理完毕,该操作才会完成。死锁!即使上下文没有将并发限制为1,但是当资源受到任何形式的限制时,这也是这样的。想象一下同样的情况,除了使用限制为4的 MaxConcurrencySynchronizationContext。与只对操作进行一次调用不同,我们将对上下文4次调用进行排队,每个调用都进行调用并阻塞等待它完成。在等待异步方法完成时,我们仍然阻塞了所有的资源,而允许这些异步方法完成的唯一事情是,它们的回调是否能被这个已经被完全消耗的上下文处理。再次,死锁!如果库方法使用 ConfigureAwait(false),它就不会将回调排队回原始上下文,从而避免了死锁的情况。

为什么我想要使用 ConfigureAwait(true)?

你不会这样做,除非你使用它纯粹是为了表明你有意不使用 ConfigureAwait(false)(例如,压制静态分析警告或类似的警告)。ConfigureAwait(true) 没有任何意义。比较 await task 和 await task.ConfigureAwait(true),在功能上是相同的。如果在生产代码中看到 ConfigureAwait(true),则可以删除它而不会产生不良影响。

ConfigureAwait 方法接受一个布尔值,因为在某些特定情况下,您需要传入一个变量来控制配置。但是99%的用例带有一个硬编码的 false 参数值,ConfigureAwait(false)。

何时应该使用 ConfigureAwait(false)?

这取决于:你是在实现应用程序级代码 app-level code 还是通用类库代码?

在编写应用程序时,通常需要默认行为(这就是为什么它是默认行为)。如果一个应用程序模型/环境(如Windows Forms, WPF, ASP. net)发布一个自定义的 SynchronizationContext,几乎可以肯定有一个很好的理由:它为关心同步上下文的代码提供了一种方式来与应用模型/环境进行适当的交互。如果你在 Windows 窗体应用程序中写一个事件处理程序,在 xunit 中写一个单元测试,在 ASP.NET MVC 控制器中写代码,无论 app 模型是否实际上发布了一个 SynchronizationContext,你都想使用那个 SynchronizationContext 如果它存在的话。这意味着默认是  ConfigureAwait(true)。只需简单地使用 await,就可以正确地将回调/延续发送回原始上下文(如果存在的话)。这就导致了以下的一般指导:如果您正在编写应用程序级别(app-level code)的代码,不要使用 ConfigureAwait(false)。如果你回想一下这篇文章前面的 Click 事件处理程序代码示例:

private static readonly HttpClient s_httpClient = newHttpClient();private async void downloadBtn_Click(objectsender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content
=text;
}

downloadBtn.Content = text 需要在原始上下文中完成。如果代码违反了这条准则,在不应该使用 ConfigureAwait(false) 的时候使用了它:

private static readonly HttpClient s_httpClient = newHttpClient();private async void downloadBtn_Click(objectsender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false); //bug downloadBtn.Content =text;
}

ASP.NET 应用依赖于 HttpContext.Current;使用 ConfigureAwait(false),然后尝试使用 HttpContext.Current 很可能会导致问题。

相反,通用库是“通用的”,部分原因是它们不关心使用它们的环境。您可以从 web 应用程序、客户端应用程序或测试中使用它们,这并不重要,因为库代码与它可能使用的应用程序模型无关。不关心也意味着它不会做任何需要以特定方式与应用模型交互的事情,例如它不会访问UI控件,因为一个通用库对UI控件一无所知。因为这样我们就不需要在任何特定的环境中运行代码,所以我们可以避免强制延续/回调到原始上下文,我们可以通过使用 ConfigureAwait(false) 来做到这一点,并获得它带来的性能和可靠性好处。这就导致了以下的一般指导:如果您正在编写通用库代码 general-purpose library code,请使用 ConfigureAwait(false)。这就是为什么 .NET Core 运行时类库中的每个 await(或者几乎每个)都使用 ConfigureAwait(false);除了少数例外,剩下的很可能是一个需要修复的错误。例如,修复了 HttpClient中 缺失的 ConfigureAwait(false) 调用。

当然,与所有指导一样,也有例外。例如,通用库中较大的例外之一(或者至少是需要思考的类别)是当这些库具有接受委托调用的 api 时。在这种情况下,库的调用者传递潜在的由库调用的应用级代码,从而有效地呈现了通用库应该“通用”的假设。例如,考虑 LINQ 的 Where 方法的异步版本, public static async IAsyncEnumerable WhereAsync(this IAsyncEnumerable source, Func<t, bool=""> predicate)。这里的 predicate 需要在调用者的原始 SynchronizationContext 上回调吗?这要由 WhereAsync 的实现来决定,这也是它选择不使用 ConfigureAwait(false) 的一个原因。

即使在这些特殊的情况下,一般的指导仍然是一个很好的起点:如果您正在编写通用的库 general-purpose library/应用程序模型无关的代码 app-model-agnostic code,那么使用 ConfigureAwait(false),否则就不需要。

ConfigureAwait(false) 是否保证回调不会在原始上下文中运行?

不。ConfigureAwait (false) 可以保证它不会被排队回原来的上下文中,但这并不意味着 await task.ConfigureAwait(false) 之后的代码不会在原来的上下文中运行。这是因为对已经完成的可等待对象的等待只是在 await 过程中同步运行,而不是强迫任何对象排队返回。因此,如果 await 一个已经完成的任务,无论是否使用 ConfigureAwait(false),紧随其后的代码都将继续在当前线程上执行,无论上下文是否仍然是当前的。

是否可以只在方法中第一个await 处使用 ConfigureAwait(false),而不是在其余的中使用?

一般来说,不行。参见前面的内容,如果等待 await task.ConfigureAwait(false) 包含一个在等待的时候已经完成的任务(这实际上是非常常见的),然后使用 ConfigureAwait (false) 将毫无意义,随着线程继续执行后面的代码上下文还是之前的上下文。

一个值得注意的例外是,如果您知道第一个 await 总是异步完成的,并且被等待的对象将在一个没有自定义 SynchronizationContext 或 TaskScheduler 的环境中调用它的回调。例如,.NET 运行时库中的 CryptoStream 希望确保其潜在的计算密集型代码不会作为调用者的同步调用的一部分运行,因此它使用自定义的 awaiter 来确保第一次await 之后的所有代码都在线程池线程上运行。然而,即使在这种情况下,您也会注意到下一个 await 仍然使用 ConfigureAwait(false);从技术上讲,这不是必需的,但是它使代码检查变得容易得多,另外,每次查看这段代码时,都不需要进行分析来理解为什么没有使用 ConfigureAwait(false)。

我可以使用 Task.Run 来避免使用 ConfigureAwait(false) 吗?

是的,如果你这样写:

Task.Run(async delegate{await SomethingAsync(); //won't see the original context
});

SomethingAsync() 的 ConfigureAwait(false) 调用将是 Nop,因为委托传递给 Task.Run 将在线程池线程上执行,而没有用户代码在堆栈的更高位置,例如 SynchronizationContext.Current 将返回 null。进一步, Task.Run 隐式使用 TaskScheduler.Default,这意味着查询 TaskScheduler.Current 的内部委托也将返回 Default。这意味着无论是否使用 ConfigureAwait(false),await 都会显示相同的行为。它也不能保证这个 lambda 中的代码可以做什么。如果你的代码:

Task.Run(async delegate{
SynchronizationContext.SetSynchronizationContext(
newSomeCoolSyncCtx());await SomethingAsync(); //will target SomeCoolSyncCtx });

然后 SomethingAsync 中的代码会看到 SynchronizationContext.Current 作为 SomeCoolSyncCtx 的实例,这两个 await 和在 SomethingAsync 任何未配置的 await 将回到它。因此,要使用这种方法,您需要了解您正在排队的所有代码可能做什么,也可能不做什么,以及它的操作是否会妨碍您的操作。

这种方法的代价是需要创建/排队额外的 task 对象。这对你的应用程序或库来说可能重要,也可能无关紧要,这取决于你的性能敏感性。

还要记住,这些技巧可能会导致比它们本身价值更多的问题,并产生其他意想不到的后果。例如,静态分析工具(如 Roslyn 分析程序)已经被用来标记不使用 ConfigureAwait(false) 的等待,比如 CA2007。如果您启用了这样的分析器,但是使用了这样的技巧来避免使用 ConfigureAwait,那么分析器很有可能会标记它,从而给您带来更多的工作。因此,您可能会因为分析器的噪音而禁用它,而现在您在代码库中丢失了实际上应该使用 ConfigureAwait(false) 的其他位置。

我可以使用 SynchronizationContext.SetSynchronizationContext 来避免使用 ConfigureAwait(false)?

不。嗯,也许吧。这取决于所涉及的代码。

一些开发人员这样写代码:

Task t;
SynchronizationContext old
=SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(
null);try{
t
= CallCodeThatUsesAwaitAsync(); //awaits in here won't see the original context }finally{ SynchronizationContext.SetSynchronizationContext(old); }await t; //will still target the original context

期望是它将使 CallCodeThatUsesAwaitAsync 内的代码看到当前上下文为空。它会的。但是,上面的操作不会影响 await 在 TaskScheduler.Current 中看到的内容。因此,如果这段代码运行在一些自定义 TaskScheduler上, CallCodeThatUsesAwaitAsync 内的 await(并没有使用 ConfigureAwait(false))仍将看到当前上下文并且排队返回到自定义 TaskScheduler。

所有相同的警告也适用于之前的 Task.Run 相关的 FAQ:这样的解决方案有其潜在的影响,并且 try 中的代码还可以通过设置不同的上下文(或使用非默认的TaskScheduler调用代码)来阻止这些意图。

对于这种模式,你还需要注意细微的变化:

SynchronizationContext old =SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(
null);try{awaitt;
}
finally { SynchronizationContext.SetSynchronizationContext(old); }

看出这个问题了吗?它很难发现,但影响很大。这里无法保证 await 最终会调用回调/延续原来的线程,这意味着 SynchronizationContext 回到最初的重置实际上可能不会发生在原始的线程,这可能会导致后续工作项在线程看到错误的上下文(要解决这个问题,现有精心编写的应用程序模型,并编写一个自定义上下文,添加一个方法,用于在进一步调用用户代码之前手动复位上下文)(to counteract this, well-written app models that set a custom context generally add code to manually reset it before invoking any further user code)。即使它碰巧在同一个线程上运行,也可能需要一段时间才能完成,因此上下文在一段时间内无法适当地恢复。如果它在不同的线程上运行,它可能会在该线程上设置错误的上下文,等等,这与想象的相去甚远。

我在用 GetAwaiter().GetResult(),我需要使用 ConfigureAwait(false) 吗?

不。ConfigureAwait 只影响回调。具体来说,awaiter 模式要求 awaiter 公开一个 IsCompleted 属性、一个 GetResult 方法和一个 OnCompleted 方法(可选的 UnsafeOnCompleted 方法)。ConfigureAwait 只影响 {Unsafe}OnCompleted 的行为,所以如果您只是直接调用 awaiter 的  GetResult() 方法,无论您是在 TaskAwaiter 上还是在 ConfiguredTaskAwaitable.ConfiguredTaskAwaiter 上执行它,对行为没有任何影响。因此,如果在代码中看到  task.ConfigureAwait(false).GetAwaiter().GetResult(),可以用 task.GetAwaiter().GetResult() 替换它(还要考虑是否真的希望这样阻塞)。

我知道我运行在一个永远不会有自定义 SynchronizationContext 或自定义 TaskScheduler 的环境中。我可以跳过使用 ConfigureAwait(false) 吗?

也许吧。这取决于你对“从不”的那部分有多肯定。正如在前面的常见问题中提到的,仅仅因为你工作的应用模型没有设置自定义 SynchronizationContext,也没有在自定义 TaskScheduler 上调用你的代码,并不意味着其他用户或库代码没有这样做。所以你需要确定事实并非如此,或者至少认识到可能存在的风险。

我听说 ConfigureAwait(false) 在.NET Core中不再需要了,真的吗?

假的。在 .NET Core 上运行时需要它,原因和在 .NET Framework 上运行时是一样的。在这方面没有什么改变。

但是,发生变化的是某些环境是否发布了它们自己的 SynchronizationContext。统的 ASP.NET 在 .NET Framework  上有自己的 SynchronizationContext,而 ASP.NET Core 则不然。这意味着在  ASP.NET Core 中运行的代码默认不会看到自定义的 SynchronizationContext,这减少了在这样的环境中运行 ConfigureAwait(false) 的需要。

但是,这并不意味着永远不会出现自定义的 SynchronizationContext 或 TaskScheduler。如果一些用户代码(或你的应用程序正在使用的其他库代码)设置了一个自定义上下文并调用你的代码,或者在调度到自定义 TaskScheduler 的 Task 中调用你的代码,那么即使在 ASP.NET Core 中你的  await 可能会看到一个非默认的上下文或调度程序,这会导致你想要使用 ConfigureAwait(false)。当然,在这种情况下,如果您避免同步阻塞(无论如何都应该避免在 web 应用程序中这样做),如果您不介意在这种有限的情况下的小的性能开销,您可能不需要使用ConfigureAwait(false)。

当我在 await foreach IAsyncEnumerable 时,我可以使用 ConfigureAwait 吗?

是的。参见 MSDN 杂志的这篇文章中的示例(https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8)。

await foreach 绑定到一个模式,因此,虽然它可以用于枚举 IAsyncEnumerable,但它也可以用于枚举公开正确 API 。. NET 运行时库包括 IAsyncEnumerable 上的一个 ConfigureAwait 扩展方法,该方法返回一个自定义类型,该类型封装了 IAsyncEnumerable和一个 Boolean 值,并公开了正确的模式。当编译器生成对枚举器的 MoveNextAsync 和 DisposeAsync 方法的调用时,这些调用是对返回的已配置枚举器结构类型的调用,然后编译器以所需的配置方式执行等待。

当我在 await using IAsyncDisposable 时,我可以使用 ConfigureAwait 吗?

是的,不过有点小麻烦。

和前面 FAQ 中描述的 IAsyncEnumerable 一样,.NET 运行时库在 IAsyncDisposable 上公开了一个 ConfigureAwait 扩展方法,并且 await using 将愉快地与之工作,因为它实现了适当的模式(即公开一个适当的 DisposeAsync 方法):

await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false))
{
...
}

这里的问题是,c 的类型现在不是 MyAsyncDisposableClass,而是 System.Runtime.CompilerServices.ConfiguredAsyncDisposable,它是 IAsyncDisposable 上的 ConfigureAwait 扩展方法返回的类型。

为了解决这个问题,你需要多写一行:

var c = newMyAsyncDisposableClass();await using (c.ConfigureAwait(false))
{
...
}

现在,c 的类型再次是所需的 MyAsyncDisposableClass。这也增加了 c 的作用域;如果这是有效的,你可以把整个代码用大括号括起来。

我使用了 ConfigureAwait(false),但是我的 AsyncLocal 仍然在 await 之后流到代码中,这是 bug 吗?

不,这是意料之中的。AsyncLocal数据流作为 ExecutionContext 的一部分,它与 SynchronizationContext 是分开的。除非您使用 ExecutionContext.SuppressFlow() 显式禁用了 ExecutionContext 流,否则无论是否使用 ConfigureAwait 来避免捕获原始的 SynchronizationContext,  ExecutionContext(以及 AsyncLocal数据)都将始终在 await 中流动。更多信息,请看这篇博客文章 https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext。

语言能帮助我避免需要在我的库中显式地使用 ConfigureAwait(false) 吗?

库开发人员有时会对需要使用 ConfigureAwait(false) 表示失望,并要求使用侵入性更小的替代方案。

目前还没有,至少没有内置到语言/编译器/运行时中。然而,对于这样的解决方案可能会是什么样子,有许多建议,例如:

https://github.com/dotnet/csharplang/issues/645, https://github.com/dotnet/csharplang/issues/2542, https://github.com/dotnet/csharplang/issues/2649,

https://github.com/dotnet/csharplang/issues/2746.

如果这对你很重要,或者你觉得你在这里有新的有趣的想法,我鼓励你在这些或新的讨论中贡献你的想法。

原文链接

https://devblogs.microsoft.com/dotnet/configureawait-faq/?utm_source=vs_developer_news&utm_medium=referral

摘要:
2023年4月8日,Kurator正式发布v0.3.0版本。

本文分享自华为云社区《
华为云 Kurator v0.3.0 版本发布!集群舰队助力分布式云统一管理
》,作者:云容器大未来 。

2023年4月8日,Kurator正式发布v0.3.0版本。

Kurator 是华为云推出的分布式云原生开源套件,通过集成业界主流开源技术栈,帮助用户一站式构建专属的分布式云原生基础设施,助力企业业务跨云跨边、分布式化升级。Kurator v0.2 版本已具备管理多云基础设施和异构基础设施的核心能力,通过引入Cluster Operator组件,支持“AWS自建集群”和“本地数据中心集群”包括集群创建、清理等在内的生命周期管理特性。

在最新发布的v0.3.0版本中,Cluster Operator不仅分别对两类集群的生命周期管理能力进行了增强,也将v0.2.0版本中的多个API对象抽象成一个API对象cluster,方便用户使用。 同时,在 cluster 对象基础上,Kurator引入了舰队的概念。每个舰队代表一个物理集群组,方便Kurator未来进行统一的编排、调度,统一的流量治理以及统一的监控运维。目前,Kurator的舰队管理支持多个特性,包括舰队控制平面的生命周期管理,以及集群注册和注销到舰队等。

至此,Kurator 通过集群舰队统一了集群视图。这意味着,Kurator开始具备了支持用户体验一致地管理分布在任何云上的集群的能力,从而进一步协助用户的分布式云原生架构的升级。

Kurator v0.3.0关键特性介绍

集群生命周期管理能力增强

Kurator 通过 Cluster Operator组件对集群的生命周期进行管理。基于Cluster API,Cluster Operator 不仅可以管理集群生命周期,还统一并简化了创建集群所需的配置,为用户在不同云平台上管理集群提供了简单易用的API。目前Cluster Operator 支持“本地数据中心集群”和“AWS自建集群”。

本地数据中心集群

Kurator基于kubespray对本地数据中心集群的生命周期进行管理。与 kubespray不同的地方在于,Kurator采用了更易于理解和配置的云原生声明式的方法管理集群。

相比较v0.2.0版本,v0.3.0版本的Kurator 带来了以下几个方面的增强特性:

  • 批量的工作节点扩缩容。
    现在Kurator支持以声明式的方式,在原有集群上增加、删除或者替换多个工作节点。 用户只需要声明集群所需要的最终工作节点状态,Kurator即可在没有任何外部干预的情况下完成节点的批量扩缩容。
  • 集群版本升级。
    用户可以在API对象上声明要升级到的Kubernetes版本,Kurator便会自动对目标集群的各节点进行升级。
  • 增强集群控制面高可用。
    Kurator为用户提供了一种基于VIP的增强集群控制面高可用的方案。在这种方案下,Kurator利用kube-vip的能力,使用VIP实现跨多个控制平面副本的入站流量负载均衡。

图片来源:
https://inductor.medium.com/say-good-bye-to-haproxy-and-keepalived-with-kube-vip-on-your-ha-k8s-control-plane-bb7237eca9fc

用户手册:
https://kurator.dev/docs/cluster-operator/vms-cluster-lifecycle/

AWS自建集群

Kurator 通过Cluster Operator对AWS自建集群进行生命周期管理管理。相较于Cluster API 对AWS 自建集群的支持, Kurator简化了Cluster API提供的部署模型,通过部署Kurator集群操作器组件即可获得全部的管理能力。

v0.3.0在以下几个方面带来了特性增强:

· 易用性提升。kurator新增了一系列提升用户体验的改进, 包括了在创建集群之前验证凭据是否有效,自动管理云平台运营商所需的IAM角色和策略,校验依赖资源是否存在以及集中展示错误信息等。

· 一键关联IAM与K8s身份。通过将AWS IAM角色与Kubernetes Pod身份关联,可以让IAM验证和接受Kubernetes颁发的令牌,而不需要创建和分发AWS凭据。这种关联具有最小特权、凭证隔离和审计性等优点,但是需要通过多个步骤设置。Kurator现在可以通过Cluster.Spec.PodIdentityg一键启用该功能,简化配置。

apiVersion: cluster.kurator.dev/v1alpha1
kind: Cluster
metadata:
name: pod
-identitynamespace: defaultspec:
...
podIdentity:
enabled:
true

用户手册:

https://kurator.dev/docs/cluster-operator/kurator-cluster-api/https://kurator.dev/docs/cluster-operator/aws-irsa/

云原生舰队管理

Kurator引入了代表物理集群组的逻辑单元“舰队”,旨在一致地管理一组集群,舰队允许您轻松、一致地管理分布在任何云中的集群。

Kurator通过Fleet Manager实现了对舰队控制平面生命周期管理,并且可以轻松的将集群添加到或从舰队中删除。 未来,Kurator将支持Fleet级的应用编排,并且在Fleet的所有集群中提供统一的命名空间、ServiceAccount、Service,以支持在多个集群之间实现服务发现和通信。此外,Kurator将汇总来自所有集群的监控指标。

Kurator Fleet Manager作为一个Kubernetes Operator运行,负责Fleet控制平面生命周期管理,也负责集群的注册和注销。

用户手册:
https://kurator.dev/docs/fleet-manager/

Kurator,一键构建分布式云原生平台

访问Kurator Release(
https://github.com/kurator-dev/kurator/releases/tag/v0.3.0
),体验、升级最新版Kurator v.0.3.0,构建您的专属分布式云原生平台。如您对Kurator新版本特性有更多兴趣或见解,也欢迎来到Kurator社区,参与社区讨论与开发。

GitHub地址:
https://github.com/kurator-dev/kurator

Slack地址:
https://join.slack.com/t/kurator-hq/shared_invite/zt-1sowqzfnl-Vu1AhxgAjSr1XnaFoogq0A

点击关注,第一时间了解华为云新鲜技术~

作者:京东科技 康志兴

前言

从强调内外隔离的六边形架构,逐渐发展衍生出的层层递进、注重领域模型的洋葱架构,再到和DDD完美契合的整洁架构。架构风格的不断演进,其实就是为了适应软件需求越来越复杂的特点。

可以看到,越现代的架构风格越倾向于清晰的职责定位,且让领域模型成为架构的核心。

基于这些架构风格,在软件架构设计过程中又有非常多的架构分层模型。

传统三层架构

传统服务通常使用三层架构:

• 门面层:作为服务暴露的入口,处理所有的外部请求。部分情况下,门面层甚至不需要单独定义对象而是直接使用服务层的实体定义。

• 服务层:作为核心业务层,包含所有业务逻辑。并对基础层能力进行简单组合提供一定的能力复用。通常服务层会进行实体定义来防止下层对象体直接暴露给外部服务,导致底层任何变化都有可能直接传递到外部,非常不稳定。

• 基础层:用来存放dao和外部rpc服务的封装,二者可以拆分为不同的module,也可合二为一,以不同package进行隔离。

三层架构特点就是简单,适用于一些无复杂业务场景的小型应用,或者“数据不可变”作为基础原则的DOP(面向数据编程)服务。

但是当业务场景稍微复杂一些、调用层级较多时,可复用性、可维护性就都非常差了,很多代码都耦合在一起,牵一发动全身。

DDD架构

DDD架构可以看做是整洁架构的一种实现,分层职责如下:

• 适配层:用来做外部不同端请求的适配器,隔离不同端的协议差异,包装不同端不同样式的响应体。

• 应用层:用例、任务入口、消息队列监听均在这一层,可以理解为业务流程的入口,通过聚合根的构造执行相应的命令操作。

• 领域服务层:包含核心的领域服务定义,并定义了gateway来做一层依赖倒置,使基础设施层仅做实现。

• 基础设施层包含一切基础能力:数据库、ES、远程调用封装等等。

优点

• 核心稳定:领域模型在依赖链上是顶层角色,不依赖任何其他模块,所以极其稳定。其他任何业务域、存储、边缘能力的变化都不会对领域模型造成影响。

• 敏捷:适合不同团队一起开发和维护而不会产生冲突。

• 可拆分:当有届上下文随着演进逐渐膨胀时,很容易拆分成微服务。

• 可扩展:添加新的功能非常简单,从而使得开发人员能够更快的部署和调整。

• 可演进:良好的可测试性带来非常低的重构成本,不会随着不断迭代导致项目成为难以修改的“大泥球”。

如此多的优点自然带来明确的缺点

• 专业性要求较高:需要对业务、架构原则理解深刻的人员进行设计和维护,不恰当的领域模型将使后续迭代极为痛苦。

• 开发成本高:复杂的架构设计,更多的架构分层,自然带来代码行数的指数级增长。尤其是项目前期的开发任务变得异常繁重。

• 不再适合简单的业务场景:实现一个简单的CRUD显得过于复杂。

• 改变决策困难:尝试使用整洁架构需要和团队的管理层和其他成员达成一致,这往往需要非常强大的说服力。如果在架构演进过程中想切换回其他架构模式也十分困难,几乎是整个项目级别的重构工作。

简单的微服务分层架构

基于六边形架构规范的接口适配原则和防腐理念,同时借鉴了CQRS模式的优点,我们定义了一个简单的微服务分层架构。

分层定义如下:

• 门面层:作为程序的入口,通过包隔离来存放JSF服务、Rest服务、定时任务和MQ消费,其中对外提供服务的接口定义存放在单独的api包中。该层的请求定义命名以Request结尾,响应体命名以Response结尾。

• 领域服务层:每一个领域服务存放在单独的module中,并通过单独的api包对外暴露能力。该层的命令请求定义命名以Command结尾,查询请求定义命名以Query结尾,响应体命名以Dto结尾。

• 基础设施层:存放数据库、ES、远程调用服务的封装。该层的持久化数据定义命名以Po结尾。远程命令服务入参命名以RpcCommand结尾,远程查询服务入参命名以RpcQuery结尾,响应体命名以RpcDto结尾。

最佳实践

  1. 命令服务必须访问领域服务层,允许简单查询直接调用基础设施层。

  2. 参数校验、请求出入参日志、审计日志记录、TraceID预埋、异常处理等非核心业务能力均由公共组件完成,减少项目内部的边缘能力代码。

  3. 由于在门面层进行统一的异常处理,非必要时无需在项目中进行大面积的try-catch,让代码更清爽。

  4. 实际开发过程中,门面层、领域服务层和基础设施层均有各自的实体定义,跨层调用的对象体转换使用
    MapStruct
    组件来减少手写映射代码的工作量。

  5. 数据层使用
    Fluent-Mybatis
    ,最大好处是减少后期迭代中,数据对象增减字段时修改Mapper的成本。

优点

1. 开发效率


简单的业务开发过程中,相比较书写核心业务逻辑,更多的工作量几乎都是来自处理跨层调用时对象转换和Mapper定义,通过MapStruct和Fluent-Mybatis等组件的使用(也许再加上GitHub Copilot