I'm trying to show a progress bar during a load operation. I know that the correct way to do this is to offload the work to a background thread and then show periodic progress updates on the UI thread. However, I have the annoying situation where the work HAS to be done on the UI thread, and as it's a 3rd party product, I can't refactor it in any way.
So ...
I've been trying to simply display a ProgressBar (in another window) on a separate thread using a new Dispatcher.
The code for this class can be seen below :
public interface ILoadingBar
{
void Stop();
string? ThreadName { get; }
}
public class LoadingBar : Window, ILoadingBar
{
public string? ThreadName => Dispatcher.Thread.Name;
public static void Start(Action<ILoadingBar> callback)
{
var thread = new Thread(() =>
{
var loadingBar = new LoadingBar()
{
Topmost = true,
ShowInTaskbar = false,
WindowStyle = WindowStyle.None,
Width = 250,
Height = 50,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
Content = new ProgressBar()
{
IsIndeterminate = true
}
};
loadingBar.Show();
callback(loadingBar);
Dispatcher.Run();
})
{
Name = "LoadingBar",
IsBackground = true
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Thread.Sleep(200);
}
public void Stop()
{
Dispatcher.BeginInvoke(() =>
{
Dispatcher.InvokeShutdown();
Close();
});
}
}
and is used like this :
private void ButtonClickOnMainThread(object sender, RoutedEventArgs e)
{
Debug.WriteLine(Dispatcher.Thread.Name);
ILoadingBar? bar = null;
LoadingBar.Start((loadingBar) =>
{
bar = loadingBar;
});
Debug.WriteLine(bar!.ThreadName);
// simulate long running work on main thread
Thread.Sleep(5000);
bar.Stop();
}
Which works fine. Until I start mixing it with async/await. When I do this, for some reason, it will often (not always) freeze when the main UI thread is doing work, and I simply can't understand why? it's almost as though the await synchronisation is locking both Dispatchers? yet the loading bar is on its own thread and its own dispatcher? how could it be interfering?
Also, annoyingly, I can't come up with a simple reproduction case. I've tried simulating what I am doing in the real app with the code below. But the code below works fine ...
private async void ButtonClickOnMainThread(object sender, RoutedEventArgs e)
{
Debug.WriteLine(Dispatcher.Thread.Name);
ILoadingBar? bar = null;
LoadingBar.Start((loadingBar) =>
{
bar = loadingBar;
});
Debug.WriteLine(bar!.ThreadName);
await SomeWork1();
await SomeWork2();
await SomeWork3();
// simulate long running work on main thread
Thread.Sleep(5000);
bar.Stop();
}
private async Task SomeWork1()
{
await Task.Run(() => Thread.Sleep(10));
}
private async Task SomeWork2()
{
await Task.Run(() => Thread.Sleep(50));
}
private async Task SomeWork3()
{
await Task.Run(() => Thread.Sleep(200));
}
Any ideas what it could possibly be? or some things I could test/change?