【问题标题】:StreamWriter uses synchronous invocation when calling asynchronous methodsStreamWriter 在调用异步方法时使用同步调用
【发布时间】:2014-03-26 02:27:06
【问题描述】:

我正在创建一个类流,它继承自 Stream。当我使用StreamWriter 类异步写入信息时:

using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
    await sw.WriteAsync(msg);

我可以看到StreamWriter 如何直接转到void Stream.Write(byte[] buffer, int offset, int count) 而不是Task Stream.WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

这是一个错误还是我遗漏了什么?

【问题讨论】:

  • 有人对正在发生的事情感到非常困惑。只要你一直在谈论 StreamReader 写文本,那某人就是你。
  • 嗯,这不仅仅是一个错字。它也出现在您的问题标题和您使用的 [标签] 中。最重要的是,你没有说过:你怎么知道这个?你在看什么?如果它是某种反汇编程序然后扔掉它,它不会给你很好的信息。
  • 我也解决了这个问题,谢谢。嗯,这和帖子所说的差不多,我可以在我的流派生类中看到Write 被调用而不是WriteAsync,这就是我当时所知道的。现在在下面的回答中,我已经解释了为什么会发生。
  • 这是async streams 中提议的IAsyncDisposable 的用例,它是C# 8.0 的候选功能,并结合了该提议未涵盖的using 语句增强功能。 C# 7.x 及更低版本没有“不错”的异步处理方式,因此来自 using 语句的隐式 Dispose() 调用会同步执行刷新。解决方法是直接调用并等待FlushAsync(),以便同步的Dispose() 不必同步刷新。
  • 在 .Net Core 上,您必须调用 DisposeAsync() 而不是 Dispose() 以避免同步刷新。

标签: async-await streamwriter .net


【解决方案1】:

我发现了混乱的根源。

StreamWriter 默认同步刷新,除非您专门异步刷新。

Stream.Flush 在关闭时被调用,当然,在刷新时。

这将在messageWriter 流中调用Write 而不是WriteAsync

using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
    await sw.WriteAsync("Hi");

StreamWriter 将缓冲“Hi”消息,并在关闭StreamWriter 时同步释放到Flush 调用的底层流中。

所以对StreamWriter.WriteAsync的异步调用可能对底层流没有影响(因为缓冲),并且在调用StreamWriter.Close时,StreamWriter.Flush被同步调用,因此底层流上的Flush方法.

这段代码也同步调用Write

using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
    sw.AutoFlush = true; // will call Write here to send the UTF8 BOM
    await sw.WriteAsync("Hi"); // Write the actual data 
}

显然也是这个:

using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
    await sw.WriteAsync("Hi"); 
    sw.Flush(); 
}

这将调用WriteAsync:

using (var sw = new StreamWriter(messageWriter, Encoding.UTF8))
{
    await sw.WriteAsync("Hi");
    await sw.FlushAsync()
}

我创建了一个简单的类来跟踪方法的调用方式:

public class MSWrite : MemoryStream
{
    public override bool CanWrite { get { return true; } }

    public override void Write(byte[] buffer, int offset, int count)
    {
        Console.WriteLine("Write");
    }

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        Console.WriteLine("WriteAsync");
        return Task.Run(() => { });
    }
}

还有这个测试:

Console.WriteLine("Simple");
using(var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
    await sw.WriteAsync("Hi");
}
Console.WriteLine();

Console.WriteLine("AutoFlush");
using (var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
    sw.AutoFlush = true;
    await sw.WriteAsync("Hi");
}
Console.WriteLine();

Console.WriteLine("Flush async");
using (var ms = new MSWrite())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
    await sw.WriteAsync("Hi");
    await sw.FlushAsync();
}

产量:

Simple
Write
Write

AutoFlush
Write
WriteAsync

Flush async
WriteAsync
WriteAsync

第一次写入的是UTF8 BOM,第二次是实际数据。

【讨论】:

    【解决方案2】:

    不,StreamWriter.WriteAsync 实际上确实调用了Stream.WriteAsync(假设您使用的是 .NET 4.5)。如果您看到其他情况,请发布堆栈跟踪。

    请注意,Stream 基类默认情况下会将WriteAsync 实现为在线程池线程上完成的Write。您需要在自定义流中覆盖 WriteAsync 才能更改此行为。

    【讨论】:

    • 看来StreamWriter.WriteAsync 根本无法调用任何东西,只能缓冲数据。由于刷新是同步发生的,这就是为什么我没有看到对 WriteAsync 的调用,但我在底层流中看到了对 Write 的调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-09
    • 2017-09-23
    • 1970-01-01
    相关资源
    最近更新 更多