【C#TAP 异步编程】构造函数 OOP
原文:异步 OOP 2:构造函数 (stephencleary.com)
异步构造带来了一个有趣的问题。能够在构造函数中使用会很有用,但这意味着构造函数必须返回一个表示将来将构造的值,而不是构造的值。这种概念很难融入现有的语言。await
Task<T>
底线是不允许构造函数,因此让我们探索一些替代方案。async
工厂模式
构造函数不能,但静态方法可以。使用静态创建方法非常容易,使类型成为自己的工厂:async
public sealed class MyClass
{
private MyData asyncData;
private MyClass() { ... } private async Task<MyClass> InitializeAsync()
{
asyncData = await GetDataAsync();
return this;
} public static Task<MyClass> CreateAsync()
{
var ret = new MyClass();
return ret.InitializeAsync();
}
} public static async Task UseMyClassAsync()
{
MyClass instance = await MyClass.CreateAsync();
...
}
可以完成所有初始化工作,但我更喜欢使用该方法。Create
async InitializeAsync
工厂方法是最常见的异步构造方法,但在某些情况下还有其他方法很有用。
AsyncLazy (for Resources)
如果要创建的实例是共享资源,则可以使用异步延迟初始化来创建共享实例:
private static AsyncLazy<MyResource> resource = new AsyncLazy<MyResource>(async () =>
{
var data = await GetResource();
return new MyResource(data);
}); public static async Task UseResourceAsync()
{
MyResource res = await resource;
}
AsyncLazy<T>
非常适合资源;在此示例中,将在第一次编辑时开始构造。任何其他方法,它将绑定到相同的结构,并且当构造完成时,所有服务员都将被释放。施工完成后的任何 s 都会立即继续,因为该值已经可用。resource
await
await
await
如果不将实例用作共享资源,则此方法不起作用。如果实例不是共享资源,则应改用另一种方法。
异步初始化模式
异步构造的最佳方法已经介绍过了:异步工厂方法和 。这些是最好的方法,因为您永远不会公开未初始化的实例。AsyncLazy<T>
但是,有时确实需要构造函数,例如,当其他组件使用反射来创建类型的实例时。这包括数据绑定、IoC 和 DI 框架等。Activator.CreateInstance
在这些情况下,必须返回未初始化的实例,但可以通过应用通用模式来缓解这种情况:每个需要异步初始化的对象都将公开一个包含异步初始化结果的属性。Task Initialization { get; }
模式
如果要将异步初始化视为实现详细信息,可以(可选)为使用异步初始化的类型定义"标记"接口:
/// <summary>
/// Marks a type as requiring asynchronous initialization and provides the result of that initialization.
/// </summary>
public interface IAsyncInitialization
{
/// <summary>
/// The result of the asynchronous initialization of this instance.
/// </summary>
Task Initialization { get; }
}
异步初始化的模式如下所示:
public sealed class MyFundamentalType : IAsyncInitialization
{
public MyFundamentalType()
{
Initialization = InitializeAsync();
} public Task Initialization { get; private set; } private async Task InitializeAsync()
{
// Asynchronously initialize this instance.
await Task.Delay(100);
}
}
这个模式非常简单,但它为我们提供了一些重要的语义:
- 初始化在构造函数中启动(当我们调用 时)。
InitializeAsync
- 初始化的完成是公开的(通过属性)。
Initialization
- 将从异步初始化引发的任何异常将被捕获并放置在属性上。
Initialization
可以(手动)构造此类型的实例,如下所示:
var myInstance = new MyFundamentalType();
// Danger: the instance is not initialized here!
await myInstance.Initialization;
// OK: the instance is initialized now.
使用异步初始化进行组合
很容易创建另一个依赖于此基本类型的类型(即异步组合):
ublic sealed class MyComposedType : IAsyncInitialization
{
private readonly MyFundamentalType _fundamental; public MyComposedType(MyFundamentalType fundamental)
{
_fundamental = fundamental;
Initialization = InitializeAsync();
} public Task Initialization { get; private set; } private async Task InitializeAsync()
{
// Asynchronously wait for the fundamental instance to initialize.
await _fundamental.Initialization; // Do our own initialization (synchronous or asynchronous).
await Task.Delay(100);
}
}
主要区别在于,我们等待所有组件初始化,然后再继续初始化。或者,您可以继续进行一些初始化,并且仅在需要完成这些特定组件时才等待这些组件。但是,每个组件都应在 末尾初始化。InitializeAsync
在撰写时,我们从此模式中获得了一些关键语义:
- 在组合类型的所有组件的初始化完成之前,其初始化不会完成。
- 组件初始化产生的任何错误都会通过组合类型显示出来。
- 组合类型支持异步初始化,并且可以像任何其他支持异步初始化的类型一样依次进行组合。
此外,如果您使用的是"标记"接口,则可以对其进行测试,并异步初始化 IoC/DI 提供给您的实例。这会稍微复杂化您的身份,但允许您将异步初始化视为实现细节。例如,如果 是 的类型:IAsyncInitialization
InitializeAsync
_fundamental
IMyFundamentalType
private async Task InitializeAsync()
{
// Asynchronously wait for the fundamental instance to initialize if necessary.
var asyncFundamental = _fundamental as IAsyncInitialization;
if (asyncFundamental != null)
await asyncFundamental.Initialization; // Do our own initialization (synchronous or asynchronous).
await Task.Delay(100);
}
顶级处理
我们已经介绍了如何使用异步初始化编写"基本"类型,以及如何通过异步初始化将它们"组合"成其他类型。最终,您将需要使用支持异步初始化的高级类型。
在许多动态创建方案(如 IoC/DI/)中,您只需直接检查并初始化它:Activator.CreateInstance
IAsyncInitialization
object myInstance = ...;
var asyncInstance = myInstance as IAsyncInitialization;
if (asyncInstance != null)
await asyncInstance.Initialization;
但是,如果您通过数据绑定创建类型,或者使用 IoC/DI 将视图模型注入到视图的数据上下文中,则您实际上没有与顶级实例交互的位置。数据绑定将在初始化完成时负责更新 UI,除非初始化失败,因此需要显示失败。遗憾的是,没有实现 ,因此任务完成不会自动显示。您可以在 AsyncEx 库中使用类似 NotifyTaskCompletion 类型的类型来简化此操作:Task
INotifyPropertyChanged
public sealed class MyViewModel : INotifyPropertyChanged, IAsyncInitialization
{
public MyViewModel()
{
InitializationNotifier = NotifyTaskCompletion.Create(InitializeAsync());
} public INotifyTaskCompletion InitializationNotifier { get; private set; }
public Task Initialization { get { return InitializationNotifier.Task; } } private async Task InitializeAsync()
{
await Task.Delay(100); // asynchronous initialization
}
}
数据绑定代码可以使用类似和响应初始化任务完成的路径。InitializationNotifier.IsCompleted
InitializationNotifier.ErrorMessage
异步初始化:结论
与异步初始化模式相比,我更喜欢异步工厂方法。异步初始化模式在初始化实例之前会公开实例,并且依赖于程序员正确使用 。但在某些情况下,您无法使用异步工厂方法,而异步初始化是一个不错的解决方法。Initialization
不该做什么
下面是一个不该执行的操作的示例:
public sealed class MyClass
{
private MyData asyncData;
public MyClass()
{
InitializeAsync();
} // BAD CODE!!
private async void InitializeAsync()
{
asyncData = await GetDataAsync();
}
}
乍一看,这似乎是一个合理的方法:你得到一个启动异步操作的常规构造函数;但是,由于使用了,因此存在一些缺点。async void
第一个问题是,当构造函数完成时,实例仍在异步初始化,并且没有明显的方法来确定异步初始化何时完成。
第二个问题是错误处理:从 引发的任何异常都将直接抛出到构造实例时的当前异常上。异常不会被围绕对象构造的任何子句捕获。大多数应用程序将此视为致命错误。InitializeAsync
SynchronizationContext
catch
本文中的前两个解决方案(异步工厂方法和 )没有这些问题。在异步初始化实例之前,它们不提供实例,并且异常处理更自然。第三种解决方案(异步初始化)确实在初始化之前返回一个实例(我不喜欢),但它通过提供一种标准方法来检测初始化何时完成以及合理的异常处理来缓解这种情况。AsyncLazy<T>
【C#TAP 异步编程】构造函数 OOP的更多相关文章
- 【C# TAP 异步编程】三、async\await的运作机理详解
[原创] 本文只是个人笔记,很多错误,欢迎指出. 环境:vs2022 .net6.0 C#10 参考:https://blog.csdn.net/brook_shi/article/details/ ...
- 【C#TAP 异步编程】异步接口 OOP
在我们深入研究"异步OOP"之前,让我们解决一个相当常见的问题:如何处理异步方法的继承?那么"异步接口"呢? 幸运的是,它确实可以很好地与继承(和接口)一起使用 ...
- 【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext
一.同步上下文(SynchronizationContext)概述 由来 多线程程序在.net框架出现之前就已经存在了.这些程序通常需要一个线程将一个工作单元传递给另一个线程.Windows程序以消息 ...
- 【C# TAP 异步编程】二 、await运算符已经可等待类型Awaitable
await的作用: 1.await是一个标记,告诉编译器生成一个等待器来等待可等待类型实例的运行结果. 2.一个await对应一个等待器 ,任务的等待器类型是TaskAwaiter/TaskAwait ...
- 【C# TAP 异步编程】一 、async 修饰符(标记)
async的作用: 1.async是一个标记,告诉编译器这是一个异步方法. 2.编译器会根据这个标志生成一个异步状态机. 3.编译器将原异步方法中的代码清空,写入状态机的配置,原先异步方法中的代码被封 ...
- C#与C++的发展历程第三 - C#5.0异步编程巅峰
系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...
- 【.NET异步编程系列1】:await&async语法糖让异步编程如鱼得水
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...
- 【异步编程】Part1:await&async语法糖让异步编程如鱼得水
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...
- 异步编程系列06章 以Task为基础的异步模式(TAP)
p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...
随机推荐
- ros实例_百度语音+图灵
1 百度语音模块 参考http://blog.csdn.net/u011118482/article/details/55001444 1.1 百度语音识别包 git clonehttps://git ...
- 如何理解python中的cmp_to_key()函数
cmp_to_key() 在functools包里的函数,将老式的比较函数(cmp function)转化为关键字函数(key function). 与接受key function的工具一同使用(如 ...
- 【转载】Systemd 入门教程:实战篇
作者: 阮一峰 日期: 2016年3月 8日 上一篇文章,我介绍了 Systemd 的主要命令,今天介绍如何使用它完成一些基本的任务. 一.开机启动 对于那些支持 Systemd 的软件,安装的时候, ...
- 对于fetch的理解
在一篇文章里见到一位作者是这么说的 XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的 ...
- Springboot整合RocketMQ解决分布式事务
直接上代码: 代码结构如下: 依次贴出相关类: DataSource1Config: package com.example.demo.config;import org.apache.ibatis. ...
- Ajax创建对象的方法
ajax涉及的技术包括Html.css.dom.xml.javascript等. 主流创建ajax对象的方法: IE6以下版本浏览器创建ajax对象方法是: 定义一个方法创建ajax对象:
- Android App发布遇到的问题总结【转】
感谢大佬:https://www.cnblogs.com/jeffen/p/6824722.html 问题描述(v1和v2) Android 7.0中引入了APK Signature Scheme ...
- Android利用zxing生成二维码
感谢大佬:https://blog.csdn.net/mountain_hua/article/details/80646089 **gayhub上的zxing可用于生成二维码,识别二维码 gayhu ...
- js 保存并排序输入内容
转载请注明来源:https://www.cnblogs.com/hookjc/ /* Create By:jiangcheng_15 Create Date:2012-01-32 */ functio ...
- centOs7.2安装cmake
最新的3.15的安装不上 wget https://cmake.org/files/v3.5/cmake-3.5.2.tar.gz tar xvf cmake-3.5.2.tar.gz cd cmak ...