" 返回《C 併發編程》" "1. 初始化共用資源" "2. Rx延遲求值" "3. 非同步數據綁定" "4. 非同步構造" "5. 非同步屬性" 1. 初始化共用資源 不管同時有多少線程調用 ,這個工廠委托只會運行一次,並且所有線程都等待同一個實例。 + 實例在創建後會被緩存起來,以後所有對 Value ...
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
把非同步代碼強制轉換為同步代碼。