-
How to do async calls without locking the UI thread in WPFIT Story/C# & WPF 2019. 11. 27. 18:29반응형
WPF developers that have worked with async await have most likely run into problems with avoiding race conditions with the UI thread, either making the entire UI lock up or burden the UI thread and cause clients not responding. This article will show you how to avoid this. The way to do this is to await using another thread and afterwards use that result back again on the WPF thread to do the updates in the UI. We use ThreadPool.QueueUserWorkItem for this. As you can see, we user an inner method marked with async keyword to await the results. This is done to avoid the classic "async proliferation" seen in async code, where the async keyword spreads upwards to all methods. We instead use an outher method and call the async method and use the Result of the Task returned. We could do some quality checking here, to check if the Task succeeded of course. The Task object contains status information about if the results are really available or not and Result will throw an exception if there was an error in the retrieval of the async results from the lower layers of the software app layers. Example code:
DispatcherUtil.AsyncWorkAndUiThreadUpdate(Dispatcher.CurrentDispatcher, () => GetSomeItems(someId), x => GetSomeItemsUpdateUIAfterwards(x), displayWaitCursor:true); //Note here that we retrieve the data not on the UI thread, but on a dedicated thread and after retrieved the //result, we do an update in the GUI. private List<SomeItemDataContract> GetSomeItems(int someId) { var retrieveTask = GomeSomeItemsInnerAsync(someId); return retrieveTask.Result; } private async Task<List<SomeItemDataContract>> GetSomeItemsInnerAsync(int someId) { List<SomeItemDataContract> retrieveTask = await SomeServiceAgent.GetSomeItems(someId); return retrieveTask; } private void GetSomeItemsUpdateUIAfterwards(SomeItemDataContract x){ if (x != null){ //Do some UI stuff - remember RaisePropertyChanged } }
public static void AsyncWorkAndUiThreadUpdate<T>(Dispatcher currentDispatcher, Func<T> threadWork, Action<T> guiUpdate, bool displayWaitCursor = false) { if (displayWaitCursor) PublishMouseCursorEvent<T>(Cursors.Wait); // ReSharper disable once UnusedAnonymousMethodSignature ThreadPool.QueueUserWorkItem(delegate(object state) { T resultAfterThreadWork = threadWork(); // ReSharper disable once UnusedAnonymousMethodSignature currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<T>(delegate { if (displayWaitCursor) PublishMouseCursorEvent<T>(Cursors.Arrow); guiUpdate(resultAfterThreadWork); }), resultAfterThreadWork); }); }
The PublishMouseCursorEvent publishes a prism event that is captured by a Bootstrapper class, but what you choose to do here is of course up to you. One way is to subscribe such an event (either a CompositePresentationEvent as in Prism or an ordinary CLR event for example):
반응형'IT Story > C# & WPF' 카테고리의 다른 글
How to Show Progress and to Cancel Asynchronous Operation Using WPF ObjectDataProvider and XAML (0) 2019.11.27 UI Thread, Dispatchers, Background Workers & Async Network Programming (0) 2019.11.27 WPF Animations and the Async/Await Model (0) 2019.11.27 C#으로 ContainsKey and ContainsValue methods (0) 2019.11.20 How to get all elements of a List (0) 2019.11.20