2

In MainWindow I have async method which displays busy Indicator

public async Task BusyLoaderAsync(Action doWorkAction)
    {
        using (var tokenSource = new CancellationTokenSource())
        {
            await loadingPanel.StartSpinAsync(tokenSource.Token).ConfigureAwait(false);
            await this.Dispatcher.InvokeAsync(doWorkAction);

            tokenSource.Cancel();

            await loadingPanel.StopSpinAsync().ConfigureAwait(false);
        }
    }

Loading Panel looks like :

<Grid Panel.ZIndex="1000" HorizontalAlignment="Stretch" Grid.RowSpan="3" Visibility="{Binding PanelLoading, Converter={StaticResource BoolToVis}}">
        <controls:LoadingPanel x:Name="loadingPanel" VerticalAlignment="Stretch"
                    IsLoading="{Binding PanelLoading}"
                    Message="Calculating..."
                    SubMessage="Wait a minute"
                               />
    </Grid>

Control Loading Panel has 2 additional methods :

public async Task StartSpinAsync(CancellationToken cancellationToken)
    {
        int delay;
        if (!int.TryParse(ConfigurationManager.AppSettings["ApplicationDelay"], out delay))
        {
            delay = 0;
        }
        await Task.Delay(delay, cancellationToken);
        await this.Dispatcher.InvokeAsync(() => IsLoading = true,
            System.Windows.Threading.DispatcherPriority.Normal, cancellationToken);
    }

    public async Task StopSpinAsync()
    {
        await this.Dispatcher.InvokeAsync(() => IsLoading = false,
            System.Windows.Threading.DispatcherPriority.Normal);
    }

We want to show loader only if it realy takes time. Cause when some task executes less than some delay - it is like blink.

When I need to show indicator I try next :

_mainWindow.BusyLoaderAsync(() => _resultViewModel.InitChart(lineChart, generalChart)).ConfigureAwait(false); // code which initialize WPF toolkit chart as example

Problem - when indicator is shown, it doesn't spining, it is freezed. I think it is because UI thread is blocked.

Is it possible to check what block UI thread and is it possible to check it from code above?

demo
  • 6,038
  • 19
  • 75
  • 149
  • 3
    A very simple check, execute your app, wait to your loader show up and not spinning, pause the execution and check the Threads panel to see where your code is paused, the first thread should be the UI thread. – Gusman Jan 11 '16 at 17:12
  • @Gusman, yes, first thread is Main Thread, which in WPF is UI Thread as I know – demo Jan 11 '16 at 17:18
  • 1
    It wasn't a question, I mean, if you go to that thread and to the call stack you will see what is blocking the UI thread... – Gusman Jan 11 '16 at 17:19
  • @Gusman, well, then I can't detect what method block UI... In CallStack I can see just this http://i.imgur.com/BmrE9gL.png and no My Method – demo Jan 11 '16 at 17:25
  • It seems like something is blocking a control creation – Gusman Jan 11 '16 at 17:29
  • Try this instead: `_mainWindow.BusyLoaderAsync(() => _resultViewModel.InitChart(lineChart, generalChart));`, note the removal of the `ConfigureAwait` – David Pine Jan 11 '16 at 17:38
  • @DavidPine, whithout it indicator is always unvisible – demo Jan 11 '16 at 17:42
  • I would expect Task.Run(doWorkAction), if you want to run it in the background. – alexm Jan 11 '16 at 17:42
  • Your UI thread, like any thread, can do only one thing at a time. It can animate your spinner *or* it can execute the doWorkAction target. It cannot do both. There is no asynchronicity in this code beyond the Delay() calls. – Hans Passant Jan 11 '16 at 18:15
  • @HansPassant, well now I am agree with You. What you can suggest ? – demo Jan 11 '16 at 19:53
  • @HansPassant, What can you said about this post http://stackoverflow.com/questions/28009151/how-does-running-several-tasks-asynchronously-on-ui-thread-using-async-await-wor ? – demo Jan 11 '16 at 20:52
  • When you do `ConfigureAwait(false)` means the task will execute on different TaskScheduler and not on the one which is associated with UI thread. When you have things to perform on UI you might want to execute respective task on UI thread. Keeping that in mind re-visit your code. – vendettamit Jan 11 '16 at 21:45

1 Answers1

0

It is problematic implementation of BusyLoaderAsync(Action doWorkAction) because it depends on doWorkAction implementation. If doWorkAction is synchronous like:

BusyLoaderAsync(() =>
{
    Debug.WriteLine(" == Action Start ");
    Thread.Sleep(8000);
    Debug.WriteLine(" == Action End ");
});

then it locks UI thread, and nothing updates in UI until doWorkAction ends. After it ends, tokenSource.Cancel(); cancels the busy indicator, and it can never updates.

If doWorkAction is asynchronous like:

BusyLoaderAsync(async () =>
{
    Debug.WriteLine(" == Action Start ");
    await Task.Delay(8000);
    Debug.WriteLine(" == Action End ");
});

then doWorkAction runs faster then StartSpinAsync and eventually tokenSource.Cancel(); cancels the busy indicator before it starts, and it also never indicates.

In synchronous case, if doWorkAction can run in another thread, BusyLoaderAsync can be as following

public async Task BusyLoaderAsync(Action doWorkAction)
{
    using (var tokenSource = new CancellationTokenSource())
    {
        var tasks = new[] { Task.Run(doWorkAction), Task.Delay(1000) };
        var result = Task.WaitAny(tasks);
        if (result == 1)
        {
            loadingPanel.IsLoading = true;
            await tasks[0];
            loadingPanel.IsLoading = false;
        }
    }
}
Igor
  • 86
  • 5