[Connect Update 16 .NET] How do we update UI if Async or Background worker is not allowed?

I was reading a thread from a few years ago that talked about an issue I am having now, I have a WPF application that has a long running process iterating files, I would prefer to run this process as a Task in a background thread, either by using backgroundworker or async, but it appears that Microsation does not allow you to load a DGN file from non-application thread.

Is there a way to update the UI and keep it responsive during this process? In VBA there's DoEvents, it doesn't release the UI but it does allow it to be refreshed/repainted. Any options on this? 

  • Hi Viktor,

    a couple of comments, some based on tests I did in the past, but some are "best guess only" ;-)

    I have a WPF application that has a long running process iterating files

    What amount of data do you process? The iteration itself is really fast: My experience is over 1 mil. of elements requires one or two seconds, depending what approach is used. So I assume something more complex is done in the iteration?

    I would prefer to run this process as a Task in a background thread, either by using backgroundworker or async

    I think this statement is wrong for more reasons:

    • Async/await construction does not relate to "background thread" topic in any way. Async/await may/may not use another thread, but it does not create a new one (or even a new process). Async/await tells how code is processed in specific synchronization context, but cannot be used to control threads or tasks explicitly.
    • To use Background worker in NET 4.6 is like to buy new car and to use steam engine to drive it. Background worker was implemented long time ago for WinForms, and is treated as obsolete. It is recommended to do not use it in new code, moreover it does not work well with some new NET features (including the discussed async/await). Use Task.Run instead of BackgroundWorker.
    but it appears that Microsation does not allow you to load a DGN file from non-application thread.

    I think that MicroStation APIs, both C++ and NET, behave like STA (Single threaded apartment). It means, every interaction with API (any call to MicroStation) must be done from MicroStation (and so addin) thread. To call API from another thread or process is bad idea and does not work (in better case) or crashes MicroStation (probably).

    In VBA there's DoEvents, it doesn't release the UI but it does allow it to be refreshed/repainted.

    It works in different way, not in terms of using different threads, because implemented in different context and technology: DoEvents stops current (WinForms, typically VB/VBA)) code, passes control to operating system to allow it to finish all tasks waiting in its queue.

    Is there a way to update the UI and keep it responsive during this process?

    I think more issues are mixed in your question, and to do not make it misleading, they should be answered separately:

    Access to DGN data is single-threaded and must be done from addin process. I am not sure whether it is possible to e.g. use DgnFile class to open another file at background from another thread or process, but I assume it's not.

    MicroStation NET API does not support async/await calls (but some code inside NET API implementation is async, especially delegates). But it does not prevent your (I guess ;-) to implement your code to be called with await. I did not test whether it brings and performance or responsiveness benefits, but I guess it depends a lot on context.

    When you need to process data, received from DGN / MicroStation API, in another thread, because you expect it slows down data iteration, you have to implement extra layer, responsible for receiving data from NET API and passing them to another thread or task (so NET API is never accessed from another thread). Fortunately, using modern NET Task / Parallel libraries, it's simpler than it was in old NET 2.

    How to update WPF GUI is completely different topic, not related to threads. MicroStation GUI is mostly standard WPF (which itself is also STA), so you can use solutions discussed e.g. on StackOverflow. If I remember right (I did some tests for one project in the past), in standard MVVM model, the standard approach is to use RelayCommand and to update GUI using Dispatcher. Alternatively, there is DispatcherHelper available in MicroStation NET API, so WPG dispatcher should not be called directly (and code is simpler a bit).

    But be aware, that GUI update is always expensive operation! WPF is not an exception, every update is internally complex task, when events are routed through object trees. When and how frequently GUI should be updated is about to balance between proper feedback and price you have to pay (how slower the code is). I remember (so numbers are approximate only) whereas iteration of 0.5 mil. of elements takes a second at max., when GUI is updated to show how many elements was iterated, the whole process grow to several minutes. My recommendation is to try to update GUI for every 1% of process, not more often.

    With regards,

      Jan

  • In VBA there's DoEvents, it doesn't release the UI

    To supplement Jan's comprehensive answer, DoEvents has nothing to do with multi-threading.  It was invented in the days of Visual Basic (VB) in the late 20th century.

    VB is a Windows app. and uses the Win32 API.  In those early days of Windows, Icons, Mice Pointer (WIMP) development, a running app. would block windows updates.  There was no option to spawn a process, so Microsoft invented DoEventsDoEvents performs message-pumping, which processes all Windows messages currently in the message queue.  That enables other windows to update themselves.

    You can do the same with C++ using mdlSystem_pumpMessages and mdlSystem_pumpMessagesEx.

     
    Regards, Jon Summers
    LA Solutions

  • Thanks Jan, I should have been a bit more clear, I am not looking to gain any performance from paralleling across threads, the main issue is updating the UI and not for every element but for every file that it opens, sort of "now scanning drawing xxxx". 

    On average this process is quick enough that we could live with it hanging, 1-2seconds, but it can be several hundred files as well where this process will be 10-20 seconds. This is where I would like the GUI to still stay somewhat responsive.

    The apps do follow mvvm structure and relay commands, and updating GUI is not an issue while not doing long processing.

    I have tried invoking a dispatcher but that hasn't worked, but I have not tried using dispatcherhelper, I will try that on Monday.

  • Hi Viktor,

    I have tried invoking a dispatcher but that hasn't worked

    it should work. But it's true (from what I remembered) it required some testing and understanding how Dispatcher work to find the right way to call current dispatcher to update particular WPF control.

    but I have not tried using dispatcherhelper,

    It should require nothing else than to call DoEvents method. When you check how the method is implemented, it (only) encapsulates calling Dispatched in the right way ;-)

    Regards,

      Jan

  • It should require nothing else than to call DoEvents method

    There are also mdlSystem_pumpMessages and mdlSystem_pumpMessagesEx.

     
    Regards, Jon Summers
    LA Solutions

  • There are also mdlSystem_pumpMessages and mdlSystem_pumpMessagesEx.

    I think they do something else and cannot be used in context of WPF addin:

    Dispatcher owns and controls WPF GUI loop and solves the problem in STA of access to the main thread from other threads/processes, where typically business logic is implemented. DispatcherHelper.DoEvents (not related to WinForms DoEvents anyhow) does nothing else than encapsulates (quite simple) Dispatcher.BeginInvoke() call, so it is more convenient to use it.

    The mentioned low-level C functions only informs OS, through Win32 API loop, that application lives (I think it does not implies GUI is refreshed). I never used it, but I guess they can be (still) useful in no GUI native code, but when WPF GUI is used, to call these functions probably does not help with GUI refresh.

    Regards,

      Jan

  • when WPF GUI is used, to call these functions probably does not help with GUI refresh

    Correct: they provide a Win32 message pump, which help to forward pending Windows messages.  They provide a similar effect to VBA DoEvents.  Like DoEvents, they have nothing to do with WPF: they were first provided a long time ago for MicroStation V8.

    I guess they can be useful

    They are useful for long-running apps. written in MDL/C++ where the UI needs to be updated periodically (e.g. a progress bar).  More importantly, they stop MicroStation apparently hanging during a long operation.  That is, they fix the problem where Windows tell us 'The application is not responding'.

     
    Regards, Jon Summers
    LA Solutions

  • Hi Victor,

    Can you give a try to the following approach?

    1. Create a new class inheriting Bentley.DgnPlatformNET.Host.

    for e.g. internal class DgnHost : Bentley.DgnPlatformNET.Host

    2. Inside your Task/Async method. Before using any DgnPlatform API, write the following.

    // Initialize Host for DgnPlatform
    Bentley.DgnPlatformNET.Host session = new DgnHost ();
    Bentley.DgnPlatformNET.DgnPlatformLib.Initialize (session, false);

    For UI

    You need to update your UI on the main thread. Try to implement some mechanism to post progress from Task/Async method.

    And on that UI update event, update your UI like:

    System.Windows.Application.Current.Dispatcher.Invoke(() =>
    {
    //code to update UI.
    });

    Please give it a try!

    Thanks,

    Mangesh


    This is a test

    Answer Verified By: Viktor_Kulik 

  • Hmmm this is interesting, its not clear as to what's going on there, but it does appear to work beautifully. Thank you very much!