>>回傳《C# 并發編程》
- 1. 初始化共享資源
- 2. Rx延遲求值
- 3. 異步資料系結
- 4. 異步構造
- 5. 異步屬性
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 = https://www.cnblogs.com/BigBrotherStone/p/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 = https://www.cnblogs.com/BigBrotherStone/p/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 = https://www.cnblogs.com/BigBrotherStone/p/NotifyTaskCompletion.Create(CalculateMyValueAsync());
}
public INotifyTaskCompletion MyValue { get; private set; }
private async Task 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 = https://www.cnblogs.com/BigBrotherStone/p/await instance.Data.Task;
System.Console.WriteLine($"PropValue:{propValue}");
});
task.Wait();
}
class AsyncProp
{
// 作為一個快取的資料,
public AsyncLazy Data { get { return _data; } }
private readonly AsyncLazy _data = new AsyncLazy(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 13;
});
}
輸出:
PropValue:13
盡量不要用 Result 或 Wait 把異步代碼強制轉換為同步代碼,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/75339.html
標籤:C#
上一篇:任務調度
下一篇:C#的多執行緒簡潔筆記
