我刚开始处理 C# 中的异步编程,我开始阅读有关异步方法和等待的内容。
在下面的代码块中,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
(但见下文),我们只需使用 WriteAsync
类似于我们如何使用 Write
:
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();
}
}
这会奏效,但不会有任何收获。但是,我们可以将 Read
替换为 await
ing ReadAsync
:
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();
}
}
对于非异步版本的更好的整体解决方案,面对可能有字符被这样的 Read
分割的编码,更简单和更有弹性,本来可以使用 ReadToEnd( )
。同样,更好的版本是使用 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(异步)与旧 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();
}
}
这显然比我们可以 await
的 XxxAsync
方法复杂得多,但它仍然比调用 BeginXxx
和然后在回调中处理 EndXxx
,尤其是在像上面的 ReadText
这样的情况下,这会导致另一个循环进入 BeginXxx
。
关于c# - 正确使用异步和等待,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30263978/