1

I am writing a WPF application. One of my view models has bindable property that holds an observable collection:

public ObservableCollection<ItemVm> Items {get; }

There is a long process, I would like to run, that modifies the observable collection, but I don't want to block the UI while its working, so I would like to split the process into a list of tasks and run them one by one. Since each of these tasks modify the collection, these tasks must run on the UI thread. I was hoping to find a way to schedule them on the UI thread in a way the allows it to perform other tasks (animations, responding to the user, etc) and only move on to the next task when the UI thread is not busy.

Having a method running in the UI thread, start another task in the UI thread is something I usually do like this:

task.Start(TaskScheduler.FromCurrentSynchronizationContext());

Except this way I can not enter any priority. I know that I could control the priority using the dispatcher but I would prefer to do it with tasks.

Kobi Hari
  • 1,259
  • 1
  • 10
  • 25
  • Do ItemVM has UI controls? – paparazzo Oct 22 '14 at 14:39
  • There are visuals that are bound to it, yes – Kobi Hari Oct 22 '14 at 15:02
  • Visuals bound and has UI controls is not the same. What is stopping you from updating the collection on a separate thread? – paparazzo Oct 22 '14 at 15:04
  • ObservableCollection can not be updated on a different thread. ItemVm objects are also updated but that can indeed be done on a different thread, but you can not call the observable collection's Add, Remove, Move methods on a different thread.It has to be the UI thread. – Kobi Hari Oct 22 '14 at 15:12
  • http://stackoverflow.com/questions/3628477/update-a-observablecollection-with-a-background-worker-in-mvvm – paparazzo Oct 22 '14 at 15:17
  • Thanks, but that's not what I'm asking. I am aware that there are ways to get an observable collection to be updated on one thread and then throw the CollectionChanged events on the UI thread. (I even have my own class called BindableCollection that does exactly that). In this case its important that the updates are done on the UI thread, which is why I just want to find a way to schedule tasks on the UI thread with low priority. – Kobi Hari Oct 22 '14 at 15:21
  • More over, as I said, I know the dispatcher can invoke actions on the UI thread in low priority but I am asking if its also possible with Tasks and TaskSchedulers. – Kobi Hari Oct 22 '14 at 15:23

1 Answers1

2

You can write a custom TaskScheduler that will execute your Tasks on the Dispatcher using a given DispatcherPriority:

class DispatcherTaskScheduler : TaskScheduler
{
    private readonly Dispatcher dispatcher;
    private readonly DispatcherPriority priority;

    public DispatcherTaskScheduler(
        Dispatcher dispatcher, DispatcherPriority priority)
    {
        this.dispatcher = dispatcher;
        this.priority = priority;
    }

    protected override void QueueTask(Task task)
    {
        dispatcher.BeginInvoke(new Action(() => TryExecuteTask(task)), priority);
    }

    protected override bool TryExecuteTaskInline(
        Task task, bool taskWasPreviouslyQueued)
    {
        // don't support inlining; inling would make sense if somebody blocked
        // the UI thread waiting for a Task that was scheduled on this scheduler
        // and we wanted to avoid the deadlock
        return false;
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        // this is only useful for debugging, so we can ignore it
        throw new NotSupportedException();
    }
}

Using the above, you can schedule a Task like this (assuming you're on the UI thread, so Dispatcher.CurrentDispatcher returns the right Dispatcher):

var dispatcherScheduler = new DispatcherTaskScheduler(
    Dispatcher.CurrentDispatcher, DispatcherPriority.Background);
var dispatcherTaskFactory = new TaskFactory(dispatcherScheduler);

var task = dispatcherTaskFactory.StartNew(() => /* your code */);

Or, if you really prefer Start():

var task = new Task(() => /* your code */);
task.Start(dispatcherScheduler);
svick
  • 236,525
  • 50
  • 385
  • 514