正确使用async和await
本文关键字:await async | 更新日期: 2023-09-27 17:49:28
我刚开始在c#中处理异步编程,我开始阅读有关异步方法和await的内容。
在下面的代码块中,WPF应用程序接受来自用户的输入,将其保存到Bin目录中的一个文件中,并将其读取回文本框。我必须使用async
方法来读写,但我还需要在WriteText
和ReadText
方法内部的方法中实现await
。
你能给我一个简短的解释我应该如何在这段代码中实现async和await的使用吗?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btnWriteFile_Click(object sender, RoutedEventArgs e)
{
await WriteFile();
}
private async void btnReadFile_Click(object sender, RoutedEventArgs e)
{
await ReadFile();
}
public async Task WriteFile()
{
string filePath = @"SampleFile.txt";
string text = txtContents.Text;
Task task1 = new Task( () => WriteTextAsync(filePath, text));
}
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
//sourceStream.BeginWrite(encodedText, 0, encodedText.Length);
await ?? sourceStream.BeginWrite(encodedText, 0, encodedText.Length, null, null);
};
}
public async Task ReadFile()
{
string filePath = @"SampleFile.txt";
if (File.Exists(filePath) == false)
{
MessageBox.Show(filePath + " not found", "File Error", MessageBoxButton.OK);
}
else
{
try
{
string text = await ReadText(filePath);
txtContents.Text = text;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
private async Task<string> ReadText(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = sourceStream.Read(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
}
}
让我们一个一个地看:
public async Task WriteFile()
{
string filePath = @"SampleFile.txt";
string text = txtContents.Text;
Task task1 = new Task( () => WriteTextAsync(filePath, text));
}
task1在这里干什么?您需要实际运行它并等待它:
public async Task WriteFile()
{
string filePath = @"SampleFile.txt";
string text = txtContents.Text;
Task task1 = new Task( () => await WriteTextAsync(filePath, text));
await task1;
}
但等等!我们正在创建一个Task
,它创建一个Task
,然后等待Task
。为什么不直接返回Task
呢?
public Task WriteFile()
{
string filePath = @"SampleFile.txt";
string text = txtContents.Text;
return WriteTextAsync(filePath, text);
}
请记住,async
使我们更容易创建在Task
中执行某些操作的方法,但如果你已经有了Task
,那么它就是浪费时间。
此外,按照惯例,您应该将异步方法命名为所有以Async
结尾的方法。这里更是如此,因为你和其他WriteTextAsync
只在签名上有所不同:
public Task WriteTextAsync()
{
return WriteTextAsync(@"SampleFile.txt", txtContents.Text);
}
实际上,如果你有一个非异步void WriteText(string filePath, string text)
,你会从一个非异步void WriteText()
调用它,这是没有什么不同的。这里没有什么新东西
现在,再看WriteTextAsync
:
因为我们现在有了任务,我们根本不需要使用旧的BeginWrite
(但见下文),我们只是像使用Write
一样使用WriteAsync
:
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
}
}
ReadFile()
是好的。让我们看看它调用了什么:
private async Task<string> ReadText(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = sourceStream.Read(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
}
这将工作,但它不会获得任何东西。但是,我们可以用ReadAsync
:
await
替换Read
。private async Task<string> ReadText(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096))
{
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();
}
}
对于非异步版本,一个更好的整体解决方案是使用ReadToEnd()
,在面对可能由这样的Read
分割字符的编码时,更简单,更有弹性。同样,这里更好的版本是使用ReadToEndAsync()
:
private async Task<string> ReadText(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096))
{
using(var rdr = new StreamReader(sourceStream, Encoding.Unicode))
{
return await rdr.ReadToEndAsync();
}
}
}
请注意,当我们返回await
执行任务的结果时,我们不能在这种情况下将其替换为return rdr.ReadToEndAsync()
,因为这样我们将在ReadToEndAsync()
实际完成之前离开using
。我们需要await
来确保我们得到了实际的结果,然后做IDisposable.Dispose()
调用,留下using
调用。
使用TPL (async)与旧APM (BeginXxx
…EndXxx
):
让我们假设我们没有stream.WriteAsync()
,必须使用stream.BeginWrite()
和stream.EndWrite()
。如果您使用的是较旧的库,可能会发生类似的情况。
我们可以使用TaskFactory.FromAsync
来创建一个Task
,它将旧的方法封装在新的方法中。因此:
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await Task.Factory.FromAsync(sourceStream.BeginWrite, sourceStream.EndWrite, encodedText, 0, encodedText.Length, null);
}
}
:
private async Task<string> ReadText(string filePath)
{
using(FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize:4096))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while((numRead = await Task<int>.Factory.FromAsync(sourceStream.BeginRead, sourceStream.EndRead, buffer, 0, buffer.Length, null)) != 0)
{
sb.Append(Encoding.Unicode.GetString(buffer, 0, numRead);
}
return sb.ToString();
}
}
这显然比仅仅使用XxxAsync
方法更复杂,我们可以await
,但它仍然比调用BeginXxx
然后在回调中处理EndXxx
更简单,特别是在像上面的ReadText
这样的情况下,这必须导致另一个循环进入BeginXxx
。
FileStream类具有用于读写流的异步方法:ReadAsync
和WriteAsync
,因此您所需要做的就是在代码中替换掉这些方法,并在它们前面加上await:
private async Task<string> ReadText(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096))
{
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();
}
}
和
private async Task WriteTextAsync(string filePath, string text)
{
byte[] encodedText = Encoding.Unicode.GetBytes(text);
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
};
}
我相信这两个方法可以进一步简化,但这应该让您开始使用异步方法。
如果你想使用一个没有async方法的类,并且你想在一个单独的线程上执行那个任务,你仍然可以这样使用async/await:
private async Task<string> ReadText(string filePath)
{
return await Task.Run(() =>
{
return File.ReadAllText("textfilepath.txt");
});
}