From a API stand point, wait for a task -- but don't block UI

320 views Asked by At

Running a longProcess() method in one of my C# api and while doing that we want to wait for that task to complete. But same time don't want to block the UI. I saw many SO threads, but nothing helps in my case. Below, Method 1 does not block UI and Method 2, blocks/freezes UI. Any thoughts?

public class MyApiManager
{
    private string _x;

    public MyApiManager(string x) 
    {
        _x = x;
    }

    public void DoProcess()
    {
        // Method 1: Does NOT block UI, but does not wait either
        Task task = Task.Factory.StartNew(() =>
        {
            Task longProcess = Task.Factory.StartNew(new Action(longProcess));
        });
        task.Wait();

        // Method 2: BLOCKS UI, also waits
        //var context = TaskScheduler.FromCurrentSynchronizationContext();
        //Task task = Task.Factory.StartNew(() =>
        //{

        //    var token = Task.Factory.CancellationToken;
        //    Task.Factory.StartNew(() =>
        //    {
        //        longProcess();
        //    }, token, TaskCreationOptions.None, context);

        //});
        //task.Wait();

    }

    private void longProcess()
    {
        // simulate long process
        Thread.Sleep(10000);
    }
}
2

There are 2 answers

1
dcastro On

First off, you need to separate your concerns.

MyApiManager should not know anything about UIs. It manages APIs, and according to the Single Responsibility Principle, that's all it does.

Read Stephen Toub's Should I expose synchronous wrappers for asynchronous methods?

It should be the UI event handler's responsibility to make sure MyApiManager does not block the UI. So, change your event handler to something like this:

public async void ButtonClick_EventHandler(object o, EventArgs args)
{
    //some code

    await Task.Run(() => apiManager.DoProcess());
    //or await Task.Run(apiManager.DoProcess);

    //some more code
}

Both of your approaches are overly complicated, you're wrapping tasks within tasks, within tasks... Simply use await Task.Run, that's all you need.

0
iheanyi On

In method 2, TaskScheduler.FromCurrentSynchronizationContext() is likely being called from within your UI code. . . so you get the UI scheduler context.

Next, you schedule your task on the UI thread and longProcess() calls sleep, so you are sleeping on the UI thread. As expected, the UI gets blocked.