与 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.ReadAsync
是 slow,我不推荐它。它是整个 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)))
.
我在 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.ReadAsync
是 slow,我不推荐它。它是整个 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)))
.