正确使用async并等待

时间:2015-05-15 16:04:07

标签: c# async-await

我刚刚开始在c#中处理异步编程,我开始阅读有关异步方法和等待的内容。

在下面的代码块中,WPF应用程序从用户获取输入,将其保存到Bin目录中的文件,然后将其读回文本框。我不得不使用async方法进行读写,但我还需要在awaitWriteText方法中的方法中实现ReadText

您能否简要介绍一下如何在此代码中实现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();
        }
    }
}

2 个答案:

答案 0 :(得分:4)

让我们一次拿一个:

public async Task WriteFile()
{
  string filePath = @"SampleFile.txt";
  string text = txtContents.Text;
  Task task1 = new Task( () =>  WriteTextAsync(filePath, text));
}

这里有什么任务1?你需要实际运行它并等待它:

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 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(),因为我们会在using之前离开ReadToEndAsync() await实际上已经完成了。我们需要IDisposable.Dispose()来确保我们获得了实际结果,然后进行using调用,留下BeginXxx调用。

在旧APM(EndXxx ... stream.WriteAsync())中使用TPL(异步):

我们假设我们没有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的情况下,这会导致另一个循环进入select avg (price) from products where price > 30

答案 1 :(得分:0)

FileStream类具有用于读取和写入流的异步方法:ReadAsyncWriteAsync,因此您需要做的就是在代码中替换这些方法,并在其前面添加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 / await时尚:

private async Task<string> ReadText(string filePath)
{
    return await Task.Run(() =>
    {
        return File.ReadAllText("textfilepath.txt");
    });
}