[英]UI blocked when using await to run a time consuming task
I want to build a folder cleaner program. 我想构建一个文件夹清理程序。 It is expected to report deleted files to a TextBox
control at real-time. 期望将删除的文件实时报告给TextBox
控件。 So I use await Task.Run(() => CleanFolder(folderPath, progress))
function in my button click event. 所以我在按钮单击事件中使用了一个await Task.Run(() => CleanFolder(folderPath, progress))
函数。 But the UI blocked when running. 但是用户界面在运行时被阻止。 After a while when the CheanFolder()
method run complete, all the deleted files are showed at one time. 当CheanFolder()
方法运行完成一段时间后,所有删除的文件会同时显示。
namespace FolderCleaner
{
public partial class MainWindow : Window
{
string folderPath;
string matchPattern;
private void ButtonOpen_Click(object sender, RoutedEventArgs e)
{
FolderBrowserDialog fbd = new FolderBrowserDialog() { Description = "Select a folder" };
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
textBoxPath.Text = folderPath;
buttonClean.IsEnabled = true;
textBoxList.Text = "Folder path: " + folderPath + "\n";
}
}
private async void ButtonClean_Click(object sender, RoutedEventArgs e)
{
matchPattern = textBoxPattern.Text;
buttonOpen.IsEnabled = false;
buttonClean.IsEnabled = false;
Progress<string> progress = new Progress<string>(msg =>
{
textBoxList.AppendText("File deleted: " + msg + "\n");
textBoxList.CaretIndex = textBoxList.Text.Length;
textBoxList.ScrollToEnd();
});
try
{
await Task.Run(() => CleanFolder(folderPath, progress));
textBoxList.AppendText("Mission complete!");
textBoxList.CaretIndex = textBoxList.Text.Length;
textBoxList.ScrollToEnd();
}
catch
{
System.Windows.MessageBox.Show("Error!");
}
finally
{
buttonOpen.IsEnabled = true;
}
}
private void CleanFolder(string path, IProgress<string> progress)
{
var filePaths = Directory.EnumerateFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
foreach (var filePath in filePaths)
{
var matchResult = Regex.Match(filePath, matchPattern);
if (matchResult.Success)
{
File.Delete(filePath);
progress.Report(filePath);
}
}
}
}
}
GUI can`t be controlled from another thread. 不能从另一个线程控制GUI。
But i think, that real problem is that concatenating of string and output to a TextBox is a very inefficient operation. 但是我认为,真正的问题是将字符串和输出连接到TextBox是非常低效的操作。
In your case it is better to show progress of removal in a single line or by using the progress bar. 在您的情况下,最好在一行中或使用进度条显示删除进度。
Here is my solution of your problem (i`ve changed 2 methods): 这是我为您解决的问题的解决方案(我更改了2种方法):
private async void ButtonClean_Click(object sender, RoutedEventArgs e)
{
matchPattern = textBoxPattern.Text;
buttonOpen.IsEnabled = false;
buttonClean.IsEnabled = false;
await Task.Run(() => CleanFolder(folderPath));
textBoxList.Text += "Mission complete!";
buttonOpen.IsEnabled = true;
}
private void CleanFolder(string path)
{
var filePaths = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories);
foreach (var filePath in filePaths)
{
var matchResult = Regex.Match(filePath, matchPattern);
if (matchResult.Success)
{
File.Delete(filePath);
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
// this working fast
textBoxList.Text = "File deleted: " + filePath + "\n";
// this working slow and slower over time
//textBoxList.Text += "File deleted: " + filePath + "\n";
textBoxList.ScrollToEnd();
});
}
}
}
I hope this will help. 我希望这将有所帮助。
Thanks for everyone. 谢谢大家 Thanks to the book C# 6.0 in a nutshell 简而言之 ,这本书要归功于C#6.0
I have figured out the solution and have a better understanding of async/await. 我已经找到解决方案,并对异步/等待有了更好的了解。
First of all, Dispatcher.Invoke
is not recommended to use since .Net Framework 4.5, task-based asynchrony has become the dominant pattern (using async/awit). 首先,由于.Net Framework 4.5不建议使用Dispatcher.Invoke
,因此基于任务的异步已成为主要模式(使用async / awit)。
Second, there are a few principles of using async/await: 其次,有一些使用异步/等待的原则:
The expression after await
must be a Task
or Task<TResult>
object await
之后的表达式必须是Task
或Task<TResult>
对象
If you use async
modifier to a method, then the method don t need to return a
Task method manually. The compile will wrap the method as a
如果对方法使用async
修饰符,则该方法t need to return a
method manually. The compile will wrap the method as a
t need to return a
Task method manually. The compile will wrap the method as a
method manually. The compile will wrap the method as a
Task` object. method manually. The compile will wrap the method as a
Task对象。
If you use a method like async Task Foo()
, you must use an await
keyword in it. 如果使用async Task Foo()
类的方法,则必须在其中使用await
关键字。
If there is nothing to await, then remove the async
modifier, return a Task
object by using return Task.Run(() => { Do Something });
如果没有什么可等待的,则删除async
修改器,使用return Task.Run(() => { Do Something });
返回一个Task
对象return Task.Run(() => { Do Something });
. 。 Now you can use await Foo()
in the method that calling Foo()
. 现在,您可以在调用Foo()
的方法中使用await Foo()
Foo()
。
Task Foo()
can not operate UI, but async Task Foo()
can. Task Foo()
无法操作UI,但async Task Foo()
可以。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.