如果 useAsync 为 true,FileStream.ReadAsync 会阻止 UI,但如果为 false,则不会阻止 UI

2024-05-02

我读到了关于useAsync参数在这个FileStream构造函数:

FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean) https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.-ctor?view=netframework-4.8#System_IO_FileStream__ctor_System_String_System_IO_FileMode_System_IO_FileAccess_System_IO_FileShare_System_Int32_System_Boolean_

我尝试使用FileStream.ReadAsync()Winforms 应用程序中的方法,如下所示:

byte[] data;
FileStream fs;
public Form1()
{
    InitializeComponent();
    fs = new FileStream(@"C:\Users\iP\Documents\Visual Studio 2015\Projects\ConsoleApplication32\ConsoleApplication32\bin\Debug\hello.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4096);
     data = new byte[(int)fs.Length];
}

private async void button1_Click(object sender, EventArgs e)
{
    await change();
}

async Task change()
{
    textBox1.Text = "byte array made";
    await fs.ReadAsync(data, 0, data.Length);
    textBox1.Text = "finished";
}

综上所述,设置的值textBox1.Text调用之前和之后的属性ReadAsync()显示在表格上。但如果我添加useAsync: true to the FileStream构造函数调用时,文本框仅显示“完成的”。文本“字节数组制作”从未显示。

文件长度为 1 GB。

我希望当启用异步 I/O 时,ReadAsync()方法将异步完成,允许 UI 线程在完成 I/O 操作之前更新文本框。相反,当异步 I/O 时not启用,我希望ReadAsync()方法同步完成,阻塞 UI 线程并且不允许在 I/O 操作完成之前更新文本框。

然而,情况似乎恰恰相反。启用异步 I/O 会阻止 UI 线程,而禁用它则允许 I/O 操作异步完成并更新 UI。

为什么是这样?


这种违反直觉的行为是我们通常认为的“异步”与 Windows 认为的“异步”之间差异的结果。前者通常意味着“去做这件事,完成后再回来找我”。对于 Windows,“异步”实际上翻译为“重叠 I/O”,即“它could是异步的”。

换句话说,在处理 Windows 时,启用“异步”操作(即“重叠 I/O”)是告诉 Windows 您的代码正在运行的方式。capable处理异步结果。它不是promise异步结果,它只是意味着如果 Windows 决定应异步完成某个操作,它可以依靠您的代码来优雅地处理该操作。否则,它将隐藏代码中的任何异步行为。

在当前的示例中,文件的全部内容(显然......这是我的测试中的情况)在文件系统缓存中可用。读取缓存数据同步地 (see 异步磁盘 I/O 在 Windows 上显示为同步 https://support.microsoft.com/en-us/help/156932/asynchronous-disk-i-o-appears-as-synchronous-on-windows),因此您所谓的“异步”操作同步完成。

当你通过时useAsync: false to the FileStream构造函数,你告诉FileStream对象在没有重叠 I/O 的情况下进行操作。与您可能的想法相反 - 您说所有操作都应该同步完成 - 事实并非如此。您只是禁用操作系统中的底层异步行为。所以当你调用像这样的异步方法时BeginRead() or ReadAsync()(前者本质上只是调用后者),FileStream对象仍然提供异步行为。但它是通过使用线程池中的工作线程来实现的,该线程又同步地从文件中读取。

因为在这种情况下您使用的是线程池线程,并且因为排队工作项总是涉及等待完成,因此无法同步完成,所以您会得到您期望的异步行为。底层的I/O操作是同步,但您看不到这一点,因为您调用了一个根据定义提供异步操作的方法,并且它通过本质上是异步的线程池来执行此操作。

请注意,即使有useAsync: true在构造函数中,至少有几种方法仍然可以看到您期望的异步行为,这两种方法都涉及文件不在缓存中。第一个很明显:自上次启动以来无需读取文件一次即可测试代码。第二个就不那么明显了。事实证明,除了定义的值之外FileOptions,还有另一个值(并且only标志中允许的另一个值):0x20000000。这对应于原生CreateFile()函数的标志名为FILE_FLAG_NO_BUFFERING.

如果您将该标志与FileOptions.Asynchronous值,你会发现ReadAsync()实际上将异步完成。

但要小心:这是有代价的。缓存的 I/O 操作通常是much比未缓存的速度更快。根据您的情况,禁用缓存可能会严重影响整体性能。同样禁用异步 I/O。允许 Windows 使用重叠 I/O 通常是good想法,并将提高性能。

如果由于重叠 I/O 操作同步完成而导致 UI 变得无响应,那么最好将该 I/O 移至工作线程,但仍会通过useAsync: true创建时FileStream对象。您将产生工作线程的开销,但对于任何相当长的 I/O 操作,与允许缓存重叠 I/O 操作所获得的性能改进相比,这将是微不足道的。

无论如何,由于我没有 1 GB 的文件可供测试,并且因为我想要对测试和状态信息有更多的控制,所以我从头开始编写了一个测试程序。下面的代码执行以下操作:

  • 如果文件尚不存在则创建该文件
  • 程序关闭时,删除该文件(如果该文件是在临时目录中创建的)
  • 显示当前时间,提供有关 UI 是否被阻止的一些反馈
  • 显示有关线程池的一些状态,这允许人们查看工作线程何时变为活动状态(即处理文件 I/O 操作)
  • 有几个复选框,允许用户更改操作模式而无需重新编译代码

观察有用的事情:

  • 当两个复选框均未选中时,I/O 始终异步完成,并显示显示正在读取的字节数的消息。请注意,在这种情况下,活动工作线程计数会增加。
  • When useAsync已检查但是disable cache未选中时,I/O 几乎总是同步完成,状态文本不更新
  • 如果两个复选框均被选中,则 I/O 始终异步完成;没有明显的方法可以将其与线程池中异步完成的操作区分开来,但不同之处在于使用的是重叠 I/O,而不是工作线程中的非重叠 I/O。注意:通常,如果您在禁用缓存的情况下进行测试,那么即使您重新启用缓存(取消选中“禁用缓存”),下一个测试仍将异步完成,因为缓存尚未恢复。

以下是示例代码(首先是用户代码,最后是设计器生成的代码):

public partial class Form1 : Form
{
    //private readonly string _tempFileName = Path.GetTempFileName();
    private readonly string _tempFileName = "temp.bin";
    private const long _tempFileSize = 1024 * 1024 * 1024; // 1GB

    public Form1()
    {
        InitializeComponent();
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        base.OnFormClosed(e);
        if (Path.GetDirectoryName(_tempFileName).Equals(Path.GetTempPath(), StringComparison.OrdinalIgnoreCase))
        {
            File.Delete(_tempFileName);
        }
    }

    private void _InitTempFile(IProgress<long> progress)
    {
        Random random = new Random();
        byte[] buffer = new byte[4096];
        long bytesToWrite = _tempFileSize;

        using (Stream stream = File.OpenWrite(_tempFileName))
        {
            while (bytesToWrite > 0)
            {
                int writeByteCount = (int)Math.Min(buffer.Length, bytesToWrite);

                random.NextBytes(buffer);
                stream.Write(buffer, 0, writeByteCount);
                bytesToWrite -= writeByteCount;
                progress.Report(_tempFileSize - bytesToWrite);
            }
        }
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        int workerThreadCount, iocpThreadCount;
        int workerMax, iocpMax, workerMin, iocpMin;

        ThreadPool.GetAvailableThreads(out workerThreadCount, out iocpThreadCount);
        ThreadPool.GetMaxThreads(out workerMax, out iocpMax);
        ThreadPool.GetMinThreads(out workerMin, out iocpMin);
        label3.Text = $"IOCP: active - {workerMax - workerThreadCount}, {iocpMax - iocpThreadCount}; min - {workerMin}, {iocpMin}";
        label1.Text = DateTime.Now.ToString("hh:MM:ss");
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        if (!File.Exists(_tempFileName) || new FileInfo(_tempFileName).Length == 0)
        {
            IProgress<long> progress = new Progress<long>(cb => progressBar1.Value = (int)(cb * 100 / _tempFileSize));

            await Task.Run(() => _InitTempFile(progress));
        }

        button1.Enabled = true;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        label2.Text = "Status:";
        label2.Update();

        // 0x20000000 is the only non-named value allowed
        FileOptions options = checkBox1.Checked ?
            FileOptions.Asynchronous | (checkBox2.Checked ? (FileOptions)0x20000000 : FileOptions.None) :
            FileOptions.None;

        using (Stream stream = new FileStream(_tempFileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, options /* useAsync: true */))
        {
            await _ReadAsync(stream, (int)stream.Length);
        }
        label2.Text = "Status: done reading file";
    }

    private async Task _ReadAsync(Stream stream, int bufferSize)
    {
        byte[] data = new byte[bufferSize];

        label2.Text = $"Status: reading {data.Length} bytes from file";

        while (await stream.ReadAsync(data, 0, data.Length) > 0)
        {
            // empty loop
        }
    }

    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        checkBox2.Enabled = checkBox1.Checked;
    }
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    this.button1 = new System.Windows.Forms.Button();
    this.progressBar1 = new System.Windows.Forms.ProgressBar();
    this.label1 = new System.Windows.Forms.Label();
    this.timer1 = new System.Windows.Forms.Timer(this.components);
    this.label2 = new System.Windows.Forms.Label();
    this.label3 = new System.Windows.Forms.Label();
    this.checkBox1 = new System.Windows.Forms.CheckBox();
    this.checkBox2 = new System.Windows.Forms.CheckBox();
    this.SuspendLayout();
    // 
    // button1
    // 
    this.button1.Enabled = false;
    this.button1.Location = new System.Drawing.Point(13, 13);
    this.button1.Name = "button1";
    this.button1.Size = new System.Drawing.Size(162, 62);
    this.button1.TabIndex = 0;
    this.button1.Text = "button1";
    this.button1.UseVisualStyleBackColor = true;
    this.button1.Click += new System.EventHandler(this.button1_Click);
    // 
    // progressBar1
    // 
    this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
    | System.Windows.Forms.AnchorStyles.Right)));
    this.progressBar1.Location = new System.Drawing.Point(13, 390);
    this.progressBar1.Name = "progressBar1";
    this.progressBar1.Size = new System.Drawing.Size(775, 48);
    this.progressBar1.TabIndex = 1;
    // 
    // label1
    // 
    this.label1.AutoSize = true;
    this.label1.Location = new System.Drawing.Point(13, 352);
    this.label1.Name = "label1";
    this.label1.Size = new System.Drawing.Size(93, 32);
    this.label1.TabIndex = 2;
    this.label1.Text = "label1";
    // 
    // timer1
    // 
    this.timer1.Enabled = true;
    this.timer1.Interval = 250;
    this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
    // 
    // label2
    // 
    this.label2.AutoSize = true;
    this.label2.Location = new System.Drawing.Point(13, 317);
    this.label2.Name = "label2";
    this.label2.Size = new System.Drawing.Size(111, 32);
    this.label2.TabIndex = 3;
    this.label2.Text = "Status: ";
    // 
    // label3
    // 
    this.label3.AutoSize = true;
    this.label3.Location = new System.Drawing.Point(13, 282);
    this.label3.Name = "label3";
    this.label3.Size = new System.Drawing.Size(93, 32);
    this.label3.TabIndex = 4;
    this.label3.Text = "label3";
    // 
    // checkBox1
    // 
    this.checkBox1.AutoSize = true;
    this.checkBox1.Location = new System.Drawing.Point(13, 82);
    this.checkBox1.Name = "checkBox1";
    this.checkBox1.Size = new System.Drawing.Size(176, 36);
    this.checkBox1.TabIndex = 5;
    this.checkBox1.Text = "useAsync";
    this.checkBox1.UseVisualStyleBackColor = true;
    this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged);
    // 
    // checkBox2
    // 
    this.checkBox2.AutoSize = true;
    this.checkBox2.Enabled = false;
    this.checkBox2.Location = new System.Drawing.Point(13, 125);
    this.checkBox2.Name = "checkBox2";
    this.checkBox2.Size = new System.Drawing.Size(228, 36);
    this.checkBox2.TabIndex = 6;
    this.checkBox2.Text = "disable cache";
    this.checkBox2.UseVisualStyleBackColor = true;
    // 
    // Form1
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(16F, 31F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(800, 450);
    this.Controls.Add(this.checkBox2);
    this.Controls.Add(this.checkBox1);
    this.Controls.Add(this.label3);
    this.Controls.Add(this.label2);
    this.Controls.Add(this.label1);
    this.Controls.Add(this.progressBar1);
    this.Controls.Add(this.button1);
    this.Name = "Form1";
    this.Text = "Form1";
    this.Load += new System.EventHandler(this.Form1_Load);
    this.ResumeLayout(false);
    this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.CheckBox checkBox1;
private System.Windows.Forms.CheckBox checkBox2;

为了解决作为评论发布的后续问题:

  1. useAsync 和 FileOptions.Asynchronous 之间有什么区别

没有任何。过载与bool参数只是为了方便而存在。它的作用完全相同。

  1. 我什么时候应该在异步方法中使用 Async : false 并使用 Async : true ?

当您想要重叠 I/O 的附加性能时,您应该指定useAsync: true.

  1. “如果您将该标志与 FileOptions.Asynchronous 值一起使用,您会发现 ReadAsync() 实际上会异步完成。”,我认为异步不会阻塞 UI,但当我使用此标志时,UI 仍然会阻塞,直到 ReadAsync 完成

这并不是一个真正的问题,但是……

看来你对我的说法有争议,包括FILE_FLAG_NO_BUFFERING in the FileOptions参数会导致ReadAsync()异步完成(这将通过禁用文件系统缓存的使用来完成)。

我无法告诉你你的计算机上发生了什么。一般来说,我希望它与我的计算机上的相同,但不能保证。我什么can告诉你的是禁用缓存,通过使用FILE_FLAG_NO_BUFFERING,在我的测试中 100% 可靠ReadAsync()异步完成。

需要注意的是,实际情况meaning国旗的不是"cause ReadAsync()异步完成”。这就是简单的副作用我观察到使用该标志。缓存并不是唯一会导致ReadAsync()同步完成,因此完全有可能即使使用该标志,您仍然会看到ReadAsync()同步完成。

无论如何,我认为这并不是真正值得关注的问题。我不认为使用FILE_FLAG_NO_BUFFERING实际上是个好主意。我已将其纳入本次讨论中only作为探索原因的一种方式ReadAsync()同步完成。我是not表明一般来说使用该标志是一个好主意。

事实上,您通常应该更喜欢重叠 I/O 的更高性能,因此应该使用useAsync: true不禁用缓存(因为禁用缓存会损害性能)。但你应该将其与also在工作线程中执行 I/O(例如,使用Task.Run()),至少在处理非常大的文件时,这样就不会阻塞 UI。

在某些情况下这可能会导致slightly总体吞吐量较低,仅仅是因为线程上下文切换。但与文件 I/O 本身相比,这种切换非常便宜,并且只要 UI 保持响应,用户甚至不会注意到。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如果 useAsync 为 true,FileStream.ReadAsync 会阻止 UI,但如果为 false,则不会阻止 UI 的相关文章

  • 使用 Xamarin.Forms 和 Zxing 生成 QR 码

    我在网上看到了很多关于这个的内容 旧帖子 但似乎没有什么对我有用 我正在尝试从字符串中生成二维码并将其显示在应用程序中 这就是我一开始的情况 qrCode new ZXingBarcodeImageView BarcodeFormat Ba
  • 如何使用 C# 以编程方式编辑 Power BI Desktop 文档参数或数据源?

    我有一个在 Power BI Desktop 中内置的报告模板 并保存为 pbix 或 pbit 文件 该模板使用DirectQuery SQL数据库作为数据源 而服务器地址和数据库名称被提取到参数中 还有一个参数包含一个ReportId
  • C# Outlook 从收件人获取 CompanyName 属性

    我目前正在使用 C 编写 Outlook 2010 AddIn 我想要的是从我从 AppointmentItem 中提取的 Recipient 对象中获取 CompanyName 属性 因此 有了 AppointmentItem 的收件人
  • 如何调整 Windows 窗体以适应任何屏幕分辨率?

    我知道这是重复的问题 但我检查了所有其他相关问题 他们的答案没有帮助 结果仍然与屏幕截图 2 中所示相同 我是 C Windows 窗体新手 如截图1所示 我有Form1有一些控件 每组控件都放在一个面板中 我在 PC1 中设计了应用程序
  • C++中的类要具备什么条件才能成为容器?

    我是 C 编程新手 偶然发现了这个术语containers举例如下vector deque map etc 一个企业的最低要求应该是什么class应该满足被称为container in C 我将从 范围 这个概念开始 Range 只有两个方
  • 具有多个谓词的 C++11 算法

    功能如std find if来自algorithmheader 确实很有用 但对我来说 一个严重的限制是我只能为每次调用使用 1 个谓词count if 例如给定一个像这样的容器std vector我想同时应用相同的迭代find if 多个
  • 为什么有些控件同时具有BackgroundImage和Image属性?

    为什么有些控件喜欢Button or PictureBox两者都有BackgroundImage and Image财产 为什么在按钮的情况下需要它们两个 或者为什么在图片框中背景图像应该可用 BackgroundImage继承自Contr
  • 检测 TextBox 中的 Tab 键按下

    I am trying to detect the Tab key press in a TextBox I know that the Tab key does not trigger the KeyDown KeyUp or the K
  • 如何设置消息队列的所有者?

    System Messaging MessageQueue 类不提供设置队列所有权的方法 如何以编程方式设置 MSMQ 消息队列的所有者 简短的答案是 p invoke 对 windows api 函数的调用MQSetQueueSecuri
  • 如何在新窗口中打开图像或pdf文件?

    我有一个 gridview 它包含文件名和文件路径 图像和 pdf 格式文件 其中我使用了模板字段 在该字段下放置了 1 个图像按钮 单击该图像按钮 即 查看 按钮 时 我想在新窗口中打开所选文件 这是我的代码 protected void
  • 从点云检测平面集

    我有一组点云 我想测试3D房间中是否有角落 所以我想讨论一下我的方法 以及在速度方面是否有更好的方法 因为我想在手机上测试它 我将尝试使用霍夫变换来检测线 然后我将尝试查看是否有三条线相交 并且它们也形成了两个相交的平面 如果点云数据来自深
  • WPF DataGrid - 在每行末尾添加按钮

    我想在数据网格的每一行的末尾添加一个按钮 我找到了以下 xaml 但它将按钮添加到开头 有人知道如何在所有数据绑定列之后添加它吗 这会将按钮添加到开头而不是末尾
  • 在 Qt 中播放通知(频率 x)声音 - 最简单的方法?

    Qt 5 1 或更高版本 我需要播放频率为 x 的通知声音 n 毫秒 如果我能像这样组合音调那就太好了 1000Hz 持续 2 秒 然后 3000Hz 持续 1 秒 最简单的方法是使用文件 WAV MP3 例如如此处所述 如何用Qt播放声音
  • 将日期时间显示为 MM/dd/yyyy HH:mm 格式 C#

    在数据库中 日期时间以 MM dd yyyy HH mm ss 格式存储 但是 我想以 MM dd yyyy HH mm 格式显示日期时间 我通过使用 String Format 进行了尝试 txtCampaignStartDate Tex
  • 不使用放置 new 返回的指针时的 C++ 严格别名

    这可能会导致未定义的行为吗 uint8 t storage 4 We assume storage is properly aligned here int32 t intPtr new void storage int32 t 4 I k
  • 时间:2019-03-17 标签:c#TimerStopConfusion

    我想通过单击按钮时更改文本颜色来将文本框文本设置为 闪烁 我可以让文本按照我想要的方式闪烁 但我希望它在闪烁几次后停止 我不知道如何在计时器触发几次后让它停止 这是我的代码 public Form1 InitializeComponent
  • 与 Entity Framework Core 2.0 的一对零关系

    我正在使用 C 和 NET Framework 4 7 将 Entity Framework 6 1 3 Code First 库迁移到 Entity Framework Core 我一直在用 Google 搜索 Entity Framew
  • 使用 boost 异步发送和接收自定义数据包?

    我正在尝试使用 boost 异步发送和接收自定义数据包 根据我当前的实现 我有一些问题 tcpclient cpp include tcpclient h include
  • 使用 IdentityDbContext 和 Code First 自动迁移表位置和架构的实体框架?

    我正在尝试使用 IdentityDbContext 类设置自动迁移更新 并将更改传播到整个数据库的实际 DbContext 在进入代码之前 在使用自动迁移实现 IdentityDbContext 时 我收到此错误 影响迁移历史系统表位置的自
  • 在 C 中使用 #define 没有任何价值

    If a define没有任何价值地使用 例如 define COMMAND SPI 默认值是0吗 不 它的评估结果为零 从字面上看 该符号被替换为空 然而 一旦你有了 define FOO 预处理器条件 ifdef FOO现在将是真的 另

随机推荐