Reputation: 13713
In a winforms application I have a method that downloads a file decompress a ZIP file. In order for it to not freeze my UI I ran the decompress command from within a task.
When the decompression task calls the ContinueWith to continue and inside that ContinueWith I call another method, which then continues my application logic. One of the methods in my logic also needs to run in a task. The problem is that when I run that method with the second task it doesn't seem like it's running in a task and my app freezes.
This is the code:
Task T = new Task(() =>
{
System.IO.Compression.ZipFile.ExtractToDirectory(
Path.Combine(_tempDirectory, "update.zip"),
Path.Combine(_tempDirectory, "update"));
});
Task T2 = T.ContinueWith((previosTask) =>
{
continueUpdate();
}, TaskScheduler.FromCurrentSynchronizationContext());
The continue method then has some logic and calls another method:
private void continueUpdate()
{
// some logic
copyTargetFiles();
}
private void copyTargetFiles()
{
Task T = new Task(() =>
{
string backupPath = Path.Combine(_tempDirectory, "Backup\\Files");
Directory.CreateDirectory(backupPath);
string sourceFolder = Shared.GetRegistryKey("Folder");
Infrastructure.Shared.CopyDirectory(sourceFolder, backupPath);
});
T.Start();
}
When it gets to copyTargetFiles the application freezes.
any suggestions? what am I doing wrong?
Upvotes: 1
Views: 1667
Reputation: 73442
This is the well known behavior, Task.Factory.StartNew
and Task.Start
are evils. They uses TaskScheduler.Current
meaning any tasks started from them will be scheduled in its parent scheduler.
In your case copyTargetFiles
is called from UI, when it starts a new Task
that is also scheduled in UI only.
How to fix:
Use Task.Run
which uses TaskScheduler.Default
which points to ThreadPool.
Task t = Task.Run(() =>
{
string backupPath = Path.Combine(_tempDirectory, "Backup\\Files");
Directory.CreateDirectory(backupPath);
string sourceFolder = Shared.GetRegistryKey("Folder");
Infrastructure.Shared.CopyDirectory(sourceFolder, backupPath);
});
I recommend you to read Task.Factory.StartNew is Dangerous, StephenCleary explains it very well.
Upvotes: 0
Reputation: 14334
You are blocking your UI thread by continuing on a specific scheduler.
TaskScheduler.FromCurrentSynchronizationContext()
By specifying a specific scheduler context, all nested tasks (regardless of being children) will use that schedule context by default. In your code, this results in the Task
started in copyTargetFiles
being run on the UI thread. Debug it and see for yourself!
You should remove the 2nd parameter of ContinueWith
(the one quoted above).
Edit: try this additional fix
private Task copyTargetFiles()
{
Task T = new Task(() =>
{
string backupPath = Path.Combine(_tempDirectory, "Backup\\Files");
Directory.CreateDirectory(backupPath);
string sourceFolder = Shared.GetRegistryKey("Folder");
Infrastructure.Shared.CopyDirectory(sourceFolder, backupPath);
});
T.Start(TaskScheduler.Default); //jumps back to the default, unrestricted scheduler context for the file copying
return T;
}
Upvotes: 1