Question

With the code below, the final UI updates made in the final ContinueWith never take place. I think it is because of the Wait() I have at the end.

The reason I am doing that is because without the Wait, the method will return the IDataProvider before its finished being constructed in the background.

Can someone help me get this right?

Cheers,
Berryl

        private IDataProvider _buildSQLiteProvider()           
    {

        IDataProvider resultingDataProvider = null;
        ISession session = null;

        var watch = Stopwatch.StartNew();

        var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

        // get the data
        var buildProvider = Task.Factory
            .StartNew(
                () =>
                    {
                        // code to build it
                    });

        // show some progress if we haven't finished
        buildProvider.ContinueWith(
            taskResult =>
            {
                // show we are making progress;
            },
            CancellationToken.None, TaskContinuationOptions.None, uiContext);

        // we have data: reflect completed status in ui 
        buildProvider.ContinueWith(
            dataProvider =>
            {
                // show we are finished;
            },
            CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext);

        try {
            buildProvider.Wait();
        }

        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
                Console.WriteLine(e.Message);
        }
        Console.WriteLine("Exception handled. Let's move on.");

        CurrentSessionContext.Bind(session);

        return resultingDataProvider;
    }

====

just to be clear

I am not having trouble talking to the ui thread. The first continue with updates the ui just fine. The trouble I am having is the timing of the last ui update and the return of the data provider.

I commented out some of the code to reduce the noise level in tis post and focus on the task sequencing.

====

ok, working code

        private void _showSQLiteProjecPicker()           
    {

        var watch = Stopwatch.StartNew();
        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        ISession session = null;

        // get the data
        var buildProvider = Task.Factory.StartNew(
                () =>
                {
                    var setProgress = Task.Factory.StartNew(
                        () =>
                        {
                            IsBusy = true;
                            Status = string.Format("Fetching data...");
                        },
                        CancellationToken.None, TaskCreationOptions.None, uiScheduler);

                    var provider = new SQLiteDataProvider();
                    session = SQLiteDataProvider.Session;
                    return provider;
                });


        buildProvider.ContinueWith(
            buildTask =>
                {
                    if(buildTask.Exception != null) {
                        Console.WriteLine(buildTask.Exception);
                    }
                    else {
                        Check.RequireNotNull(buildTask.Result);
                        Check.RequireNotNull(session);

                        _updateUiTaskIsComplete(watch);

                        CurrentSessionContext.Bind(session);
                        var provider = buildTask.Result;
                        var dao = provider.GetActivitySubjectDao();
                        var vm = new ProjectPickerViewModel(dao);
                        _showPicker(vm);
                    }
                },
            CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
    }
Was it helpful?

Solution

UPDATE BELOW
This code doesn't look like it warrants TPL to me. Looks like maybe a good use for a BackgroundWorker instead!

Either way, the updates are probably not taking place because you can't update the UI from a separate thread -- you need to run the update on the UI thread. You should use the Dispatcher for this (http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms contains info for both WPF and WinForms)

Update:

So I obviously missed some of the code so here's a revised answer. First of all, Nicholas is correct -- .ContinueWith returns a new task (http://msdn.microsoft.com/en-us/library/dd270696.aspx). So instead of

var result = Task.Factory.StartNew(...);
result.ContinueWith(...);

you probably want to create a new task and then make all the ContinueWith() calls and assign to the task and then call .Start() on the task. Something like:

var task = new Task(...).ContinueWith(...);
task.Start();

However, there is a flaw in the design to begin with (as I see it)! You're trying to run this code async, wihch is why you're using threads and TPL. However, you're calling buildProvider.Wait(); on the UI thread which blocks the UI thread until this task completes! Aside from the issue of repainting the UI in the ContinueWith() while the UI thread is blocked, there's no benefit to multithreading here since you're blocking the UI thread (a major no-no). What you probably want to do is stick the Bind()-ing inside a ContinueWith or something so that you don't have to call Wait() and block the UI thread.

My $0.02 is that if you expect the query to take a long time what you really want is 2 threads (or tasks in TPL)-- one to perform the query and one to update the UI at intervals with status. If you don't expect it to take so long I think you just want a single thread (Task) to query and then update the UI when it's done. I would probably do this via BackgroundWorker. TPL was built for managing lots of tasks and continuations and such but seems overkill for this kind of thing -- I think you could do it using a BackgroundWorker in a lot less code. But you mention you want to use TPL which is fine, but you're going to have to rework this a bit so that it actually runs in the background!

PS - you probably meant to put the Console.WriteLine("Exception handled. Let's move on."); inside the catch

OTHER TIPS

I'm a little hazy, but last time I used the TPL I found it confusing. ContinueWith() returns a new Task instance. So you need to assign the second ContinueWith() result to a new variable, say var continuedTask = builderProvider.ContinueWith(...), and then change the last one to reference continuedTask.ContinueWith() instead of buildProvider.ContinueWith(). Then Wait() on the last Task.

Hope that helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top