1:使用async和await异步编程
常用使用场景如下:
Web 访问 | HttpClient | SyndicationClient |
使用文件 | StreamWriter, StreamReader, XmlReader | StorageFile |
使用图像 | MediaCapture, BitmapEncoder, BitmapDecoder | |
WCF 编程 | 同步和异步操作 |
方法名前加async编程异步方法,方法返回类型 task task<TResult> void,需要使用异步方法结果用await 等待异步返回的结果;没有用await异步方法会作为同步方法执行。
async和await不会创建新线程,也不会阻止当前线程,异步方法不会在自身线程上运行,异步设计原理就是非阻塞操作,await等待的时候呢,会把操作权返回给调用方。
调用async方法的时候会在后台去线程池里调用闲置的线程执行方法,await就会挂起等待当前异步方法返回结果。
2:使用Task.WhenAll扩展异步方法
当有多个异步需要执行到时候,可以使用Task.WhenAll执行异步集合,当所有异步方法在里面执行完后,Task.WhenAll才会结束。
C#复制
// Create a query. IEnumerable<Task<int>> downloadTasksQuery = from url in urlList select ProcessURL(url, client); // Use ToArray to execute the query and start the download tasks. Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
// Await the completion of all the running tasks.
int[] lengths = await Task.WhenAll(downloadTasks);
3:使用Async和Await并行发起web请求
就是在使用await前,在启动任务和等待任务之间可以发起多次请求。其它任务都会以隐式的方式运行,不会创建新线程。
C#复制
private async Task CreateMultipleTasksAsync()
{
// Declare an HttpClient object, and increase the buffer size. The
// default buffer size is 65,536.
HttpClient client =
new HttpClient() { MaxResponseContentBufferSize = 1000000 };
// Create and start the tasks. As each task finishes, DisplayResults
// displays its length.
Task<int> download1 =
ProcessURLAsync("http://msdn.microsoft.com", client);
Task<int> download2 =
ProcessURLAsync("http://msdn.microsoft.com/library/hh156528(VS.110).aspx", client);
Task<int> download3 =
ProcessURLAsync("http://msdn.microsoft.com/library/67w7t67f.aspx", client);
// Await each task.
int length1 = await download1;
int length2 = await download2;
int length3 = await download3;
int total = length1 + length2 + length3;
// Display the total count for the downloaded websites.
resultsTextBox.Text +=
string.Format("\r\n\r\nTotal bytes returned: {0}\r\n", total);
}
4:异步控制流
用到关键字await异步就将控制器返回调用方。
C#复制
public partial class MainWindow : Window
{
// . . .
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// ONE
Task<int> getLengthTask = AccessTheWebAsync();
// FOUR
int contentLength = await getLengthTask;
// SIX
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
// TWO
HttpClient client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("http://msdn.microsoft.com");
// THREE
string urlContents = await getStringTask;
// FIVE
return urlContents.Length;
}
}
5:微调异步应用程序
1. 取消一个异步任务或者一组异步任务
不想等待一个异步任务完成,可以通过设置来取消该任务。
取消单个和取消列表类似:
C#复制
// ***Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
// ***Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
// ***Send a token to carry the message if cancellation is requested.
int contentLength = await AccessTheWebAsync(cts.Token);
cts.Cancel();
2. 在一段时间后取消异步任务
如果不希望等待操作结束,可使用 CancellationTokenSource.CancelAfter 方法在一段时间后取消异步操作。 此方法会计划取 消未在
CancelAfter
表达式指定的时间段内完成的任何关联任务。C#复制
CancellationTokenSource cts; // Instantiate the CancellationTokenSource. cts = new CancellationTokenSource(); // ***Set up the CancellationTokenSource to cancel after 2.5 seconds. (You // can adjust the time.) cts.CancelAfter(2500); await AccessTheWebAsync(cts.Token);
3.在完成一个任务后取消其余任务
通过结合使用 Task.WhenAny 方法和 CancellationToken,可在一个任务完成时取消所有剩余任务。 WhenAny
方法采用任务集合中的一个参数。 该方法启动所有任务,并返回单个任务。 当集合中任意任务完成时,完成单个任务。
C#复制
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
await AccessTheWebAsync(cts.Token);
cts = null;
if (cts != null)
{
cts.Cancel();
}
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url, client, ct);
// ***Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
// ***Call WhenAny and then await the result. The task that finishes
// first is assigned to firstFinishedTask.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Cancel the rest of the downloads. You just want the first one.
cts.Cancel();
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
4. 启动多个异步任务并在其完成时进行处理
通过使用 Task.WhenAny,可以同时启动多个任务,并在它们完成时逐个对它们进行处理,而不是按照它们的启动顺序进行处理。
C#复制
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
await AccessTheWebAsync(cts.Token);
cts = null;
if (cts != null)
{
cts.Cancel();
}
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
}
async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
6. 处理异步重入问题
三种方式:
- 按钮点击后变为禁用;
- 二次进入马上取消先前执行的任务,执行当前任务;
- 进入等待队列,依次执行;
C#复制
// ***Disable the Start button until the downloads are complete.
StartButton.IsEnabled = false;
try
{
await AccessTheWebAsync();
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.";
}
// ***Enable the Start button in case you want to run the program again.
finally
{
StartButton.IsEnabled = true;
}
C#复制
// *** Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
// This line is commented out to make the results clearer in the output. //ResultsTextBox.Clear(); // *** If a download process is already underway, cancel it. if (cts != null) { cts.Cancel(); } // *** Now set cts to cancel the current process if the button is chosen again. CancellationTokenSource newCTS = new CancellationTokenSource(); cts = newCTS; try { // ***Send cts.Token to carry the message if there is a cancellation request. await AccessTheWebAsync(cts.Token); } // *** When the process is complete, signal that another process can proceed. if (cts == newCTS) cts = null;
C#复制
// ***Declare the following variables where all methods can access them. private Task pendingWork = null; private char group = (char)('A' - 1);
// ***Verify that each group's results are displayed together, and that
// the groups display in order, by marking each group with a letter.
group = (char)(group + 1);
ResultsTextBox.Text += string.Format("\r\n\r\n#Starting group {0}.", group);
try
{
// *** Pass the group value to AccessTheWebAsync.
char finishedGroup = await AccessTheWebAsync(group);
// The following line verifies a successful return from the download and
// display procedures.
ResultsTextBox.Text += string.Format("\r\n\r\n#Group {0} is complete.\r\n", finishedGroup);
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.";
}
private async Task<char> AccessTheWebAsync(char grp)
{
HttpClient client = new HttpClient();
// Make a list of the web addresses to download.
List<string> urlList = SetUpURLList();
// ***Kick off the downloads. The application of ToArray activates all the download tasks.
Task<byte[]>[] getContentTasks = urlList.Select(url => client.GetByteArrayAsync(url)).ToArray();
// ***Call the method that awaits the downloads and displays the results.
// Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.
pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp);
ResultsTextBox.Text +=
string.Format("\r\n#Task assigned for group {0}. Download tasks are active.\r\n", grp);
// ***This task is complete when a group has finished downloading and displaying.
await pendingWork;
// You can do other work here or just return.
return grp;
}
private async Task FinishOneGroupAsync(List<string> urls, Task<byte[]>[] contentTasks, char grp)
{
// ***Wait for the previous group to finish displaying results.
if (pendingWork != null)
await pendingWork;
int total = 0;
// contentTasks is the array of Tasks that was created in AccessTheWebAsync.
for (int i = 0; i < contentTasks.Length; i++)
{
// Await the download of a particular URL, and then display the URL and
// its length.
byte[] content = await contentTasks[i];
DisplayResults(urls[i], content, i, grp);
total += content.Length;
}
// Display the total count for all of the websites.
ResultsTextBox.Text += tring.Format("\r\n\r\nTOTAL bytes returned: {0}\r\n", total);
}
7. 使用异步进行文件访问
可使用异步功能访问文件。 通过使用异步功能,你可以调用异步方法而无需使用回调,也不需要跨多个方法或 lambda 表达式来拆分代码。 若要使同步代码异步,只需调用异步方法而非同步方法,并向代码中添加几个关键字。
可能出于以下原因向文件访问调用中添加异步:
-
异步使 UI 应用程序响应速度更快,因为启动该操作的 UI 线程可以执行其他操作。 如果 UI 线程必须执行耗时较长的代码(例如超过 50 毫秒),UI 可能会冻结,直到 I/O 完成,此时 UI 线程可以再次处理键盘和鼠标输入及其他事件。
-
异步可减少对线程的需要,进而提高 ASP.NET 和其他基于服务器的应用程序的可伸缩性。 如果应用程序对每次响应都使用专用线程,同时处理 1000 个请求时,则需要 1000 个线程。 异步操作在等待期间通常不需要使用线程。 异步操作仅需在结束时短暂使用现有 I/O 完成线程。
-
当前条件下,文件访问操作的延迟可能非常低,但以后可能大幅增加。 例如,文件可能会移动到覆盖全球的服务器。
-
使用异步功能所增加的开销很小。
-
异步任务可以轻松地并行运行。
C#复制
//写文件
public async void ProcessWrite() { string filePath = @"temp2.txt"; string text = "Hello World\r\n"; await WriteTextAsync(filePath, text); } private async Task WriteTextAsync(string filePath, string text) { byte[] encodedText = Encoding.Unicode.GetBytes(text); using (FileStream sourceStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) { await sourceStream.WriteAsync(encodedText, 0, encodedText.Length); }; }
//读文件
public async void ProcessRead()
{
string filePath = @"temp2.txt";
if (File.Exists(filePath) == false)
{
Debug.WriteLine("file not found: " + filePath);
}
else
{
try
{
string text = await ReadTextAsync(filePath);
Debug.WriteLine(text);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
//并行IO
private async Task<string> ReadTextAsync(string filePath)
{
using (FileStream sourceStream = new
FileStream(filePath, FileMode.Open, FileAccess.Read,
FileShare.Read,bufferSize: 4096, useAsync: true))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
}
public async void ProcessWriteMult()
{
string folder = @"tempfolder\";
List<Task> tasks = new List<Task>();
List<FileStream> sourceStreams = new List<FileStream>();
try
{
for (int index = 1; index <= 10; index++)
{
string text = "In file " + index.ToString() + "\r\n";
string fileName = "thefile" + index.ToString("00") + ".txt";
string filePath = folder + fileName;
byte[] encodedText = Encoding.Unicode.GetBytes(text);
FileStream sourceStream = new FileStream(filePath,
FileMode.Append, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true);
Task theTask = sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
sourceStreams.Add(sourceStream);
tasks.Add(theTask);
}
await Task.WhenAll(tasks);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}