Josh Russo
Josh Russo

Reputation: 3241

Why would a long running Task still block the UI?

I'm trying to resolve a problem where my UI is being blocked and I don't understand why.

public Task AddStuff(string myID, List<string> otherIDs)
{
    Action doIt = () =>
    {
        this.theService.AddStuff(myID, otherIDs);
    };

    return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}

If the list is long the call can take 30 seconds and the entire application becomes unresponsive (goes that washed out white in Windows 7).

Is there a different way to do this so it doesn't block the UI?


Edit

Ok, so there's a LOT of code around this I'm going to try to keep this pertinent. I did realize going back to the original code, that I had removed something that may have been important. Should I maybe use a different TaskScheduler than TaskScheduler.Current?

Also there are no Wait statements impeding any of this code, and the service doesn't interact with the UI.

Task.Factory.StartNew(objState =>
    {
        LoadAssets(objState);
    }, state, this.cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);

private void LoadAssets(object objState)
{
    LoadAssetsState laState = (LoadAssetsState)objState;

    List<string> assetIDs = new List<string>();

    for (int i = 0; i < laState.AddedMediaItems.Count; i++)
    {
        if (laState.CancellationToken.IsCancellationRequested)
            return;

        string assetId = this.SelectFilesStep.AssetService.GetAssetId(laState.AddedMediaItems[i], laState.ActiveOrder.OrderID);

        assetIDs.Add(assetId);

    }

    if (laState.CancellationToken.IsCancellationRequested)
        return;

    this.ApiContext.AddAssetToProduct(laState.ActiveOrder.OrderID, laState.ActiveProduct.LineID, assetIDs, laState.Quantity, laState.CancellationToken).ContinueWith(task =>
    {
        if (laState.CancellationToken.IsCancellationRequested)
            return;


        App.ApiContext.GetOrderDetails(laState.ActiveOrder.OrderID, false, laState.CancellationToken).ContinueWith(orderDetailsTask =>
        {
            if (laState.CancellationToken.IsCancellationRequested)
                return;

            this.activeOrder = orderDetailsTask.Result;

            this.StandardPrintProductsStep.Synchronize(this.activeOrder);

        });
    });
}

public Task AddAssetToProduct(string orderID, string lineID, List<string> assetIDs, int quantity, CancellationToken? cancellationToken = null)
{
    Action doIt = () =>
    {
        if (cancellationToken.IsCancellationRequested())
            return;

        this.ordersService.AddAssetToProduct(orderID, lineID, assetIDs, quantity);
    };

    if (cancellationToken != null)
        return Task.Factory.StartNew(doIt, cancellationToken.Value, TaskCreationOptions.LongRunning, TaskScheduler.Current);
    else
        return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}

EDIT

I have placed break points just before and after the service call and it is the service call that is blocking the UI, as opposed to any other line.

It sounds like there is no reason this should be blocking, so I think I'm just going to break the list down if it's long and make multiple calls. I just wanted to make sure I wasn't missing something here with my Task logic.

Upvotes: 3

Views: 2161

Answers (2)

James Manning
James Manning

Reputation: 13579

Wish I could put formatted code in a comment, but since I don't see how, adding this snippet as an answer. This is the kind of approach I'd use to figure out whether the task is running on the UI thread or not (since you don't want it to) and have the action be something completely different (a simple thread.sleep).

var state = new object();
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

var task = Task.Factory.StartNew(
    objState => { Console.WriteLine ("Current thread is {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(30); },
    state,
    cancellationToken,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Current);

task.Wait();    

Upvotes: 0

Reed Copsey
Reed Copsey

Reputation: 564413

Is there a different way to do this so it doesn't block the UI?

This call, in and of itself, should not block the UI. If, however, theService.AddStuff does some synchronization with the UI's SynchronizationContext, this could cause the UI to effectively be blocked by that call.

Otherwise, the problem is likely happening from outside of this function. For example, if you call Wait() on the task returned from this method, in a UI thread, the UI thread will be blocked until this completes.


You probably want to use TaskScheduler.Default, not TaskScheduler.Current. If this is being called within a Task that's scheduled on a TaskScheduler based on the UI thread, it will schedule itself on the UI thread.

Upvotes: 6

Related Questions