1. 初始化共享资源
不管同时有多少线程调用 GetSharedIntegerAsync
,这个工厂委托只会运行一次,并且所有线程都等待同一个实例。
- 实例在创建后会被缓存起来,以后所有对 Value 属性的访问都返回同一个实例。
public static void UtilShareRun()
{
// 示例1: 100次并行调用,只输出一次,验证了 只被执行一次 和 线程安全性
Parallel.For(0, 100, (i, s) =>
{
UtilShare share = new UtilShare();
share.GetSharedIntegerAsync().Wait();
});
// 示例2: 显示出调度线程号的切换情况
// 示例3: 执行前已经调用了 share.GetSharedIntegerAsync()
// 那么后面无论是否设置 ConfigureAwait 后面是不会发生上下文切换的,因为已经是直接拿到结果了
// share.GetSharedIntegerAsync().Wait();
// AsyncContext.Run(async () =>
// {
// UtilShare share = new UtilShare();
// System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] before.");
// await share.GetSharedIntegerAsync()
// //.ConfigureAwait(false);
// ;
// System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] after.");
// });
}
public class UtilShare
{
static int _simpleValue;
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(async () =>
{
System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}]");
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
// 只输出一次
System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] " + nameof(MySharedAsyncInteger));
return _simpleValue++;
});
public async Task GetSharedIntegerAsync()
{
int sharedValue = await MySharedAsyncInteger.Value;
}
}
示例1 输出:
; 使用当前上下文调用
[1]
; 因为设置了 ConfigureAwait 导致上下文不延续,后面交给线程池线程执行
[18] MySharedAsyncInteger
示例2 输出:
[1] before.
[1]
[4] MySharedAsyncInteger
; 因为 await share.GetSharedIntegerAsync();延续了上下文
; 所以此处恢复了调用前是一个上下文
; 如果设置为不延续,则此处线程号会是线程池线程
[1] after.
示例3 输出:
; 第一次执行
[1]
[4] MySharedAsyncInteger
; 因为已经有结果了,后面不会造成上下文切换
[1] before.
[1] after.
本例中委托返回一个 Task<int>
对象,就是一个用异步方式得到的整数值。
-
不管有多少代码段同时调用
Value
,Task<int>
对象只会创建一次,并且每个调用都返回同一个对象 -
每个调用者可以用
await
调用这个Task
对象,(异步地)等待它完成
Lazy 委托中的代码会在当前同步上下文中运行。
如果有几种不同类型的线程会调用 Value(例如一个 UI 线程和一个线程池线程,或者两个不同的 ASP.NET 请求线程),那最好让委托只在线程池线程中运行。这实现起来很简单,只要把工厂委托封装在 Task.Run
调用中:
public static void UtilShareTaskRun()
{
Parallel.For(0, 100, (i, s) =>
{
UtilShareTask share = new UtilShareTask();
share.GetSharedIntegerAsync().Wait();
});
}
public class UtilShareTask
{
static int _simpleValue;
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(() =>
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
// 只输出一次
System.Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] " + nameof(MySharedAsyncInteger));
return _simpleValue++;
})
);
public async Task GetSharedIntegerAsync()
{
int sharedValue = await MySharedAsyncInteger.Value;
}
}
输出:
[19] MySharedAsyncInteger
2. Rx延迟求值
想要在每次被订阅时就创建一个新的源 observable 对象
- 例如让每个订阅代表一个不同的 Web 服务请求。
Rx 库有一个操作符Observable.Defer
(初始化时会执行委托)
- 每次 observable 对象被订阅时,它就会执行一个委托。
- 该委托相当于是一个创建 observable 对象的工厂
public static void UtilDeferRun()
{
var invokeServerObservable = Observable.Defer(() => GetValueAsync().ToObservable());
invokeServerObservable.Subscribe(_ => { });
// invokeServerObservable.Subscribe(_ => { });
Thread.Sleep(2000);
}
static async Task<int> GetValueAsync()
{
Console.WriteLine("Calling server...");
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine("Returning result...");
return 13;
}
输出:
Calling server...
Returning result...
注意: 如果对 Defer
后的 observable 对象 await
或者 Wait()
也会被触发订阅。
3. 异步数据绑定
在异步地检索数据时,需要对结果进行数据绑定(例如绑定到 Model-View-ViewModel 设计模式中的 ViewModel)。
可以使用 AsyncEx 库中的 NotifyTaskCompletion
类:
class MyViewModel
{
public MyViewModel()
{
MyValue = NotifyTaskCompletion.Create(CalculateMyValueAsync());
}
public INotifyTaskCompletion<int> MyValue { get; private set; }
private async Task<int> CalculateMyValueAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
return 13;
}
}
可以绑定到 INotifyTaskCompletion<T>
属性中的各种属性,如下所示:
<Grid>
<Label Content="Loading..."Visibility="{Binding MyValue.IsNotCompleted,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Label Content="{Binding MyValue.Result}"Visibility="{Binding MyValue.IsSuccessfullyCompleted,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Label Content="An error occurred" Foreground="Red"Visibility="{Binding MyValue.IsFaulted,Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
也可以自己编写数据绑定的封装类代替 AsyncEx 库中的类。下面的代码介绍了基本思路:
class BindableTask<T> : INotifyPropertyChanged
{
private readonly Task<T> _task;
public BindableTask(Task<T> task)
{
_task = task;
var _ = WatchTaskAsync();
}
private async Task WatchTaskAsync()
{
try
{
await _task;
}
catch { }
OnPropertyChanged("IsNotCompleted");
OnPropertyChanged("IsSuccessfullyCompleted");
OnPropertyChanged("IsFaulted");
OnPropertyChanged("Result");
}
public bool IsNotCompleted
{
get
{
return !_task.IsCompleted;
}
}
public bool IsSuccessfullyCompleted
{
get
{
return _task.Status == TaskStatus.RanToCompletion;
}
}
public bool IsFaulted { get { return _task.IsFaulted; } }
public T Result
{
get
{
return IsSuccessfullyCompleted ? _task.Result : default(T);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
4. 异步构造
异步初始化模式
public static void AsyncConstructionRun()
{
var task = Task.Run(async () =>
{
IMyFundamentalType instance = new MyFundamentalType();
System.Console.WriteLine("Instance created.");
var instanceAsyncInit = instance as IAsyncInitialization;
if (instanceAsyncInit != null)
{
await instanceAsyncInit.Initialization;
System.Console.WriteLine("Instance Initialized.");
}
});
task.Wait();
}
interface IMyFundamentalType { }
interface IAsyncInitialization
{
Task Initialization { get; }
}
class MyFundamentalType : IMyFundamentalType, IAsyncInitialization
{
public MyFundamentalType()
{
Initialization = InitializeAsync();
}
public Task Initialization { get; private set; }
private async Task InitializeAsync()
{
System.Console.WriteLine("MyFundamentalType initializing.");
// 对这个实例进行异步初始化。
await Task.Delay(TimeSpan.FromSeconds(1));
System.Console.WriteLine("MyFundamentalType initialized.");
}
}
输出:
MyFundamentalType initializing.
Instance created.
MyFundamentalType initialized.
Instance Initialized.
可以对这种模式进行扩展,将类和异步初始化结合起来。下面的例子定义了另一个类,它以前面建立的 IMyFundamentalType
为基础:
public static void AsyncConstructionsRun()
{
AsyncInitialization.WhenAllInitializedAsync(new MyComposedType(new MyFundamentalType()), new MyComposedType(new MyFundamentalType())).Wait();
}
class MyComposedType : IAsyncInitialization
{
private readonly IMyFundamentalType _fundamental;
public MyComposedType(IMyFundamentalType fundamental)
{
_fundamental = fundamental;
Initialization = InitializeAsync();
}
public Task Initialization { get; private set; }
private async Task InitializeAsync()
{
System.Console.WriteLine("MyComposedType initializing.");
// 如有必要,异步地等待基础实例的初始化。
var fundamentalAsyncInit = _fundamental as IAsyncInitialization;
if (fundamentalAsyncInit != null)
await fundamentalAsyncInit.Initialization;
// 做自己的初始化工作(同步或异步)。...
System.Console.WriteLine("MyComposedType initialized.");
}
}
public static class AsyncInitialization
{
public static Task WhenAllInitializedAsync(params object[] instances)
{
return Task.WhenAll(instances.OfType<IAsyncInitialization>().Select(x => x.Initialization));
}
}
输出:
MyFundamentalType initializing.
MyComposedType initializing.
MyFundamentalType initializing.
MyComposedType initializing.
MyFundamentalType initialized.
MyComposedType initialized.
MyFundamentalType initialized.
MyComposedType initialized.
5. 异步属性
如果每次访问属性都会启动一次新的异步操作,那说明这个“属性”其实应该是一个方法。
public static void UtilPropRun()
{
var instance = new AsyncProp();
var task = Task.Run(async () =>
{
var propValue = await instance.Data.Task;
System.Console.WriteLine($"PropValue:{propValue}");
});
task.Wait();
}
class AsyncProp
{
// 作为一个缓存的数据。
public AsyncLazy<int> Data { get { return _data; } }
private readonly AsyncLazy<int> _data = new AsyncLazy<int>(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 13;
});
}
输出:
PropValue:13
尽量不要用 Result
或 Wait
把异步代码强制转换为同步代码。