developer82
developer82

Reputation: 13713

Task ContinueWith calls a method with another task

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

Answers (2)

Sriram Sakthivel
Sriram Sakthivel

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

Gusdor
Gusdor

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

Related Questions