此文为Async in C#5.0学习笔记
1、在async/await之前的异步
方式一:基于事件的异步Event-based Asynchronous Pattern (EAP).
private void DumpWebPage(Uri uri)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += OnDownloadStringCompleted;
webClient.DownloadStringAsync(uri);
}
private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs eventArgs)
{
m_TextBlock.Text = eventArgs.Result;
}
方式二:基于IAsyncResult接口的异步
private void LookupHostName()
{
object unrelatedObject = "hello";
Dns.BeginGetHostAddresses("oreilly.com", OnHostNameResolved, unrelatedObject);
}
private void OnHostNameResolved(IAsyncResult ar)
{
object unrelatedObject = ar.AsyncState;//上文中的hello字符串
IPAddress[] addresses = Dns.EndGetHostAddresses(ar);
// Do something with addresses
}
方式三:回调
void GetHostAddress(string hostName, Action<IPAddress> callback)
{
//..
}
private void LookupHostName2()
{
GetHostAddress("oreilly.com", OnHostNameResolved);
}
private void OnHostNameResolved(IPAddress address)
{
// Do something with address
}
private void LookupHostName3()
{
GetHostAddress("oreilly.com", address =>
{
// Do something with address and aUsefulVariable
});
}
方式四:使用Task,尤其是Task<T>
private void LookupHostNameByTask()
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync("oreilly.com");
ipAddressesPromise.ContinueWith(_ =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
Console.WriteLine("In ContinueWith");//后输出
});
Console.WriteLine("After ContinueWith");//先输出
}
共同的缺陷:必须将方法分为两部分
乱如麻的递归
private void LookupHostNames(string[] hostNames)
{
LookUpHostNamesHelper(hostNames, 0);
}
private static void LookUpHostNamesHelper(string[] hostNames, int i)
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync(hostNames[i]);
ipAddressesPromise.ContinueWith(_ =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
if (i + 1 < hostNames.Length)
{
LookUpHostNamesHelper(hostNames, i + 1);
}
});
}
2、使用Async
private async void DumpWebPageAsync(string uri)
{
WebClient webClient = new WebClient();
string page = await webClient.DownloadStringTaskAsync(uri);
Console.WriteLine(page);
}
async/await
Task<string> myTask = webClient.DownloadStringTaskAsync(uri);
// Do something here
string page = await myTask;
注意,下面这样操作可能会有隐患,当firstTask有异常时,将不执行await secondTask
Task<string> firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com");
Task<string> secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com");
string firstPage = await firstTask;
string secondPage = await secondTask;
3、Async方法的返回类型
1、void
2、Task
3、Task<T>
4、Async方法具有传递性
private async Task<int> GetPageSizeAsync(string url)
{
WebClient webClient = new WebClient();
string page = await webClient.DownloadStringTaskAsync(url);
return page.Length;
}
private async Task<string> FindLargestWebPage(string[] urls)
{
string largest = null;
int largestSize = 0;
foreach (string url in urls)
{
int size = await GetPageSizeAsync(url);
if (size > largestSize)
{
size = largestSize;
largest = url;
}
}
return largest;
}
5、Async匿名委托和Lambdas表达式
Func<Task<int>> getNumberAsync = async delegate { return 3; };
Func<Task<string>> getWordAsync = async () => "hello";
6、await实际上做了啥?
同时执行下面两个分支
1、从当前行返回跳出函数,执行其他代码
2、等待await异步执行完成后,继续执行当行后面的语句
注意不恰当的大量使用异步,会必正常使用同步慢。
6、哪里不能使用await
1、catch和finally块
/// <summary>
/// 错误的写法
/// </summary>
private async void InvalidMethodAsync()
{
var webClient = new WebClient();
string page;
try
{
page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}
}
/// <summary>
/// 正确的写法
/// </summary>
private async void InsteadMethodAsync()
{
bool failed = false;
var webClient = new WebClient();
string page;
try
{
page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
}
catch (WebException)
{
failed = true;
}
if (failed)
{
page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
}
}
2、lock块
不在lock内使用await
private async void LockAsync()
{
var sync = new object();
lock (sync)
{
// Prepare for async operation
}
int myNum = await Task.Run(() => { Thread.Sleep(5000); return 1; });
lock (sync)
{
// Use result of async operation
}
}
3、LINQ查询表达式
private async void LINQQueryAsync()
{
var alexsInts = new List<int>();
IEnumerable<Task<int>> tasks = alexsInts
.Where(x => x != 9)
.Select(async x => await DoSomthingAsync(x) + await DoSomthingElseAsync(x));
IEnumerable<int> transformed = await Task.WhenAll(tasks);
}
private async Task<int> DoSomthingAsync(int x)
{
return await Task.Run(() => { return x * 2; });
}
private async Task<int> DoSomthingElseAsync(int x)
{
return await Task.Run(() => { return x + 2; });
}
4、Unsafe Code
7、异常捕获
异步内的异常不会直接抛出,可通过Task的IsFaulted判断
private async void ExceptionCaptureAsync()
{
var task = ThrownExceptionAsync();
Console.WriteLine($"1 task.IsFaulted={task.IsFaulted}");
try
{
var result=await task;
Console.WriteLine("Finished");
}
catch(Exception ex)
{
Console.WriteLine($"2 Exception:{ex.Message}");
}
Console.WriteLine("3 Exit ExceptionCaptureAsync");
}
private async Task<int> ThrownExceptionAsync()
{
throw new Exception("In ThrownExceptionAsync");
}
输出结果:
8、async方法在需要之前是同步的
一般来说,async方法,在遇到第一个await之前是同步的。
其他情况:
- 获取task的result
- 未到达await时已经return
- 运行了异步代码(但已执行完成)
- 到达await时,异步方法也已执行完成
9、基于任务的异步模式(Task-Based Asynchronous Pattern,TAP)
同步方法
- 有或没有参数,尽量避免ref与out参数
- 有返回类型
- 正确的方法名
- 常见或预期的异常是返回类型的一部分,意外异步应该把抛出
如:
public static IPHostEntry GetHostEntry(string hostNameOrAddress)
异步方法
- 有或没有参数,不能有ref或out参数
- 返回类型为Task或Task<T>
- 命名格式为NameAsync(以Async结尾)
- 错误的使用应直接抛出,其他异常在Task内处理
public static async Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
10、使用Task进行计算密集型操作
Task t = Task.Run(() => MyLongComputation(a, b));
await Task.Run(() => MyLongComputation(a, b));
private async Task<int> TaskDemoAsync()
{
int a = 0, b = 0;
CancellationToken cancellationToken = new CancellationToken();
CustomTaskScheduler taskScheduler = new CustomTaskScheduler();
var t1 = Task.Factory.StartNew(() => { return MyLongComputation(a, b); },
cancellationToken,
TaskCreationOptions.LongRunning,
taskScheduler);
return await t1;
}
private static int MyLongComputation(int a,int b)
{
return a + b;
}
//https://www.infoworld.com/article/3063560/how-to-build-your-own-task-scheduler-in-csharp.html
public sealed class CustomTaskScheduler : TaskScheduler, IDisposable
{
private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>();
private readonly Thread mainThread = null;
public CustomTaskScheduler()
{
mainThread = new Thread(new ThreadStart(Execute));
if (!mainThread.IsAlive)
{
mainThread.Start();
}
}
private void Execute()
{
foreach (var task in tasksCollection.GetConsumingEnumerable())
{
TryExecuteTask(task);
}
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return tasksCollection.ToArray();
}
protected override void QueueTask(Task task)
{
if (task != null)
tasksCollection.Add(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
private void Dispose(bool disposing)
{
if (!disposing) return;
tasksCollection.CompleteAdding();
tasksCollection.Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
11、创建Puppet Task
//没有PermissionDialog类
//没有async关键词
private Task<bool> GetUserPermission()
{
// Make a TaskCompletionSource so we can return a puppet Task
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// Create the dialog ready
PermissionDialog dialog = new PermissionDialog();
// When the user is finished with the dialog, complete the Task using SetResult
dialog.Closed += delegate { tcs.SetResult(dialog.PermissionGranted); };
// Show the dialog
dialog.Show();
// Return the puppet Task, which isn't completed yet
return tcs.Task;
}
private async void GetUserPermissionAsync()
{
if(await GetUserPermission())
{
//Do something
}
}
12、旧的异步交互
IAsyncResult BeginGetHostEntry(string hostNameOrAddress,
AsyncCallback requestCallback,
object stateObject)
IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
{
TaskCompletionSource<IPHostEntry> tcs = new TaskCompletionSource<IPHostEntry>();
Dns.BeginGetHostEntry(hostNameOrAddress, asyncResult =>
{
try
{
IPHostEntry result = Dns.EndGetHostEntry(asyncResult);
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
}, null);
return tcs.Task;
}
private async void FromAsyncDemo()
{
string hostNameOrAddress = "www.baidu.com";
var t = Task<IPHostEntry>.Factory.FromAsync<string>(Dns.BeginGetHostEntry,
Dns.EndGetHostEntry,
hostNameOrAddress,
null);
await t;
Console.WriteLine($"Dns:{t.Result.AddressList.FirstOrDefault()}");
}
13、延时(Task.Delay)
一般延时
await Task.Run(() => Thread.Sleep(100));
使用Timer和TaskCompletionSource(直接使用Task.Delay即可)
private static Task Delay(int millis)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
System.Threading.Timer timer = new System.Threading.Timer(_ => tcs.SetResult(null), null, millis, Timeout.Infinite);
tcs.Task.ContinueWith(delegate { timer.Dispose(); });
return tcs.Task;
}
14、等待所有Task完成(Task.WhenAll)
private async void TaskWhenAllDemoAsync()
{
List<Task<string>> tasks = new List<Task<string>>();
List<string> domains = new List<string>() { "1", "2", "3" };
var rnd = new Random((int)DateTime.Now.Ticks);
foreach (string domain in domains)
{
tasks.Add(GetStringAsync(domain, rnd.Next(100, 1000)));
}
//等价
//var tasks2 = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000)));
var allTask = Task.WhenAll(tasks);
var taskResult = await allTask;
foreach (var str in taskResult)
{
Console.WriteLine($"domain:{str}");
}
}
private async Task<string> GetStringAsync(string domain, int sleepTime)
{
var task = Task.Run(() =>
{
Task.Delay(sleepTime);
Console.WriteLine($"Delay:{sleepTime}ms in Domain:{domain}");
return domain;
});
return await task;
}
输出结果:
15、等待Task列表中的任一个完成(Task.WhenAny)
public static Task WhenAny(IEnumerable tasks)
private async void WhenAnyDemoAsync()
{
var domains = new List<string>() { "1", "2", "3" };
var rnd = new Random((int)DateTime.Now.Ticks);
var tasks = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000))).ToList();
//注意上面与下面运行的结果差别
// The IEnumerable from Select is lazy, so evaluate it to start the tasks
//var tasks = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000)));
Task<Task<string>> anyTask=Task.WhenAny(tasks);//注意,如果没有ToList(),当前才执行tasks内的GetStringAsync函数
Task<string> winner=await anyTask;
string strDomain=await winner;
Console.WriteLine($"Domain:{strDomain}");
await Task.WhenAll(tasks);//注意,如果没有ToList,这里的task与上面的是两次结果
Console.WriteLine($"============================\r\n");
}
16、自定义组合
超时时退出
private async Task<T> WithTimeout<T>(Task<T> task, int time)
{
Task delayTask = Task.Delay(time);
Task firstToFinish = await Task.WhenAny(task, delayTask);
if (firstToFinish == delayTask)
{
// The delay finished first - deal with any exception
_ = task.ContinueWith(HandleException);
throw new TimeoutException();
}
return await task; // If we reach here, the original task already finished
}
private static void HandleException<T>(Task<T> task)
{
if (task.Exception != null)
{
//Log Exception
//Logging.LogException(task.Exception);
}
}
17、取消异步操作
CancellationTokenSource cts = new CancellationTokenSource();
cancelButton.Click += delegate { cts.Cancel(); };
int result = await dbCommand.ExecuteNonQueryAsync(cts.Token);
foreach (var x in thingsToProcess)
{
cancellationToken.ThrowIfCancellationRequested();//then IsCanceled will be true
// Process x ...
}
18、异步操作期间返回进度
使用 IPRogress<T>
Task<byte[]> DownloadDataTaskAsync(Uri address,
CancellationToken cancellationToken,
IProgress<DownloadProgressChangedEventArgs> progress)
new Progress<int>(percentage => progressBar.Value = percentage);
private Task<byte[]> DownloadDataTaskAsync(Uri address,
CancellationToken cancellationToken,
IProgress<int> progress)
{
var currPercent = 1;
progress.Report(currPercent);
return null;
}
private void ReturnProgress()
{
var progress= new Progress<int>(percentage => progressBar1.Value = percentage);
progress.ProgressChanged += Progress_ProgressChanged;
}
19、异步操作的生命周期
private async void btn_GetIcon_Click(object sender, EventArgs e)
{
Console.Clear();
Enqueue($"1\tStart in btn_GetIcon_Click");
var task = GetFavIcon("www.csdn.net");
Enqueue($"3\tStart await GetFavIcon");
var ico = await task;
Enqueue($"6\tFinish await in btn_GetIcon_Click,{ico}");
while(queue.TryDequeue(out string result))
{
Console.WriteLine(result);
}
}
private async Task<string> GetFavIcon(string url)
{
Enqueue($"2\tIn GetFavIcon function");
var task = Task.Run(() =>
{
Enqueue($"3\tStart to GetfavIcon for {url}");
var spendTime = rndNum.Next(1000, 1500);
Task.Delay(spendTime);
Enqueue($"4\tSpend {spendTime}ms To Getfavicon for {url}");
return url;
});
Enqueue($"3\tStart await in GetFavicon");
var result = await task;
Enqueue($"5\tFinish await in GetFavicon");
return $"favicon:{result}";
}
private void Enqueue(string msg)
{
queue.Enqueue($"{DateTime.Now:HH:mm:ss.FFFFFFF}\t{msg}");
}
输出结果:
序号为3的输出顺序可能不一致。
20、选择不使用SynchronizationContext(ConfigureAwait)
没太明白
21、异步方法的Result属性
var result = AlexsMethodAsync().Result;
程序会阻塞等待AlexsMethodAsync执行结束
22、异步的异常
async Task CatcherAsync()
{
try
{
Console.Clear();
Console.WriteLine("In CatcherAsync");
var task = Thrower();
Console.WriteLine("Before await Thrower");
await task;
Console.WriteLine("After await Thrower");
}
catch (Exception ex)
{
Console.WriteLine($"Cathc Exception in Catcher:{ex.Message}");
}
}
async Task Thrower()
{
await Task.Delay(100);
throw new Exception("Exception in Thrower");
}
private async void btn_ExceptionDemo_Click(object sender, EventArgs e)
{
await CatcherAsync();
}
输出结果:
23、使用.ContinueWith(HandleException)处理void类型异步
public static void ForgetSafely(this Task task)
{
task.ContinueWith(HandleException);
}
private static void HandleException(Task task)
{
//do something task.Exception
}
24、AggregateException和WhenAll
Task<Image[]> allTask = Task.WhenAll(tasks);
try
{
await allTask;
}
catch
{
foreach (Exception ex in allTask.Exception.InnerExceptions)
{
// Do something with exception
}
}
25、尽量在同步模块时抛出异常
private Task<Image> GetFaviconAsync(string domain)
{
if (domain == null) throw new ArgumentNullException("domain");
return GetFaviconAsyncInternal(domain);
}
private async Task<Image> GetFaviconAsyncInternal(string domain)
{
//...
}
26、异步里,finally不一定执行
await DelayForever()及finally里的代码没有执行
async void AlexsMethod()
{
try
{
Console.WriteLine("Before DelayForever");
await DelayForever();
Console.WriteLine("After DelayForever");
}
finally
{
// Never happens
Console.WriteLine("finally,After DelayForever");
}
}
Task DelayForever()
{
Console.WriteLine("IN DelayForever");
return new TaskCompletionSource<object>().Task;
}
27、lock块内不能使用await
lock (sync)
{
// Prepare for async operation
}
int myNum = await AlexsMethodAsync();
lock (sync)
{
// Use result of async operation
}
if (DataInvalid())
{
Data d = await GetNewData();
// Anything could have happened in the await
if (DataInvalid())
{
SetNewData(d);
}
}