与 CancellationToken 一起异步读取文件

Reading file asynchronously along with CancellationToken

我在 WinForms 中有一个从文本文件导入的按钮。一旦开始导入,它将按钮的文本更改为 Processing... 并实例化一个 CancellationToken,如果用户请求,它应该取消操作。如果在导入过程中再次按下按钮,它应该会激活任务取消。

有很多方法可以做到这一点,但我无法决定使用哪一种。什么是最干净的方法?

private CancellationTokenSource _cts;
private List<string> _list = new List<string>();

private async void Button_Click(object sender, EventArgs e)
{
    if (button.Text == "Processing...")
    {
        if (_cts != null)
        {
            _cts.Cancel();
            _cts.Dispose();
        }
    }
    else
    {
        using var dialog = new OpenFileDialog
        {
            Filter = "All Files (*.*)|*.*"
        };

        if (dialog.ShowDialog() == DialogResult.OK)
        {
            button.Text = "Processing...";
            
            _cts = new CancellationTokenSource();

            var fileStream = dialog.OpenFile();

            using var reader = new StreamReader(fileStream);

            try
            {
                int count = 0;
                while (!reader.EndOfStream)
                {
                    var line = await reader.ReadLineAsync().WithCancellation(_cts.Token).ConfigureAwait(false);

                    Trace.WriteLine(line);
                    _list.Add(line);

                    count++;
                }
            }
            catch (TaskCanceledException)
            {
            }

            //await Task.Run(async () =>
            //{
            //    int count = 0;
            //    while (!reader.EndOfStream)
            //    {
            //        var line = await reader.ReadLineAsync().ConfigureAwait(false);
            //        //var line = reader.ReadLine();

            //        Trace.WriteLine(line);

            //        count++;
            //    }
            //});

            //await Task.Factory.StartNew(() =>
            //{
            //    int count = 0;
            //    while (!reader.EndOfStream)
            //    {
            //        var line = reader.ReadLine();

            //        Trace.WriteLine(line);

            //        count++;
            //    }
            //});

            BeginInvoke(new Action(() => button.Text = "Process"));
        }
    }
}

public static class ThreadExtensionMethods
{
    public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
    {
        return task.IsCompleted // fast-path optimization
            ? task
            : task.ContinueWith(
                completedTask => completedTask.GetAwaiter().GetResult(),
                cancellationToken,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
    }
}

因为 FileStream.ReadAsyncslow,我不推荐它。它是整个 async 文件系统 API 的底层方法。让我们 运行 它在 Task.

private CancellationTokenSource _cts;
private List<string> _list = new List<string>();

private async void button1_Click(object sender, EventArgs e)
{
    if (_cts != null)
    {
        _cts.Cancel();
    }
    else
    {
        using var dialog = new OpenFileDialog
        {
            Filter = "All Files (*.*)|*.*"
        };

        if (dialog.ShowDialog() == DialogResult.OK)
        {
            button.Text = "Processing...";
            using (_cts = new CancellationTokenSource())
            {
                await Task.Run(() =>
                {
                    using var reader = new StreamReader(dialog.OpenFile());
                    int count = 0;
                    while (!reader.EndOfStream && !_cts.Token.IsCancellationRequested)
                    {
                        var line = reader.ReadLine();

                        Trace.WriteLine(line);
                        _list.Add(line);

                        count++;
                    }
                });
            }
            _cts = null;
            button.Text = "Process";
        }
    }
}

请注意 List 不是 Thread-safe。确保在加载数据时您没有对其进行任何操作。如果这样做,请考虑来自 System.Collections.Concurrent 命名空间的一些集合。

您也可以避免在此处代理收集 _list.Add(line) 并内联处理数据或将其添加到 ListBox,如您在评论中所说。像这样:this.Invoke((Action)(() => listBox.Add(line))).