c# - How to let the UI refresh during a long running *UI* operation -
before flag question being duplicate, hear me out.
most people have long running non-ui operation doing , need unblock ui thread. have long running ui operation must run on ui thread blocking rest of application. basically, dynamically constructing dependencyobject
s @ run time , adding them ui component on wpf application. number of dependencyobject
s need created depends upon user input, of there no limit. 1 of test inputs have has 6000 dependencyobject
s need created , loading them takes couple minutes.
the usual solution of using background worker in case not work, because once dependencyobject
s created background worker, can no longer added ui component since created on background thread.
my current attempt @ solution run loop in background thread, dispatch ui thread each unit of work , calling thread.yield()
give ui thread chance update. works - ui thread chance update couple times during operation, application still blocked.
how can application keep updating ui , processing events on other forms during long running operation?
edit: requested, example of current 'solution':
private void initializeform(list<nondependencyobject> mycollection) { action<nondependencyobject> dowork = (nondepobj) => { var dependencyobject = createdependencyobject(nondepobj); uicomponent.add(dependencyobject); // set binding on each dependencyobject , update progress bar ... }; action background = () => { foreach (var nondependencyobject in mycollection) { if (nondependencyobject.needstobeadded()) { dispatcher.invoke(dowork, nondependencyobject); thread.yield(); //doesn't give ui enough time update } } }; background.begininvoke(background.endinvoke, null); }
changing thread.yield()
thread.sleep(1)
seems work, solution?
sometimes indeed required background work on ui thread, particularly, when majority of work deal user input.
example: real-time syntax highlighting, as-you-type. might possible offload sub-work-items of such background operation pool thread, wouldn't eliminate fact text of editor control changing upon every new typed character.
help @ hand: await dispatcher.yield(dispatcherpriority.applicationidle)
. give user input events (mouse , keyboard) best priority on wpf dispatcher event loop. background work process may this:
async task douithreadworkasync(cancellationtoken token) { var = 0; while (true) { token.throwifcancellationrequested(); await dispatcher.yield(dispatcherpriority.applicationidle); // ui-related work this.textblock.text = "iteration " + i++; } }
this keep ui responsive , background work fast possible, idle priority.
we may want enhance throttle (wait @ least 100 ms between iterations) , better cancellation logic:
async task douithreadworkasync(cancellationtoken token) { func<task> idleyield = async () => await dispatcher.yield(dispatcherpriority.applicationidle); var cancellationtcs = new taskcompletionsource<bool>(); using (token.register(() => cancellationtcs.setcanceled(), usesynchronizationcontext: true)) { var = 0; while (true) { await task.delay(100, token); await task.whenany(idleyield(), cancellationtcs.task); token.throwifcancellationrequested(); // ui-related work this.textblock.text = "iteration " + i++; } } }
updated op has posted sample code.
based upon code posted, agree @highcore's comment proper viewmodel.
the way you're doing currently, background.begininvoke
starts background operation on pool thread, synchronously calls ui thread on tight foreach
loop, dispatcher.invoke
. adds overhead. besides, you're not observing end of operation, because you're ignoring iasyncresult
returned background.begininvoke
. thus, initializeform
returns, while background.begininvoke
continues on background thread. essentially, fire-and-forget call.
if want stick ui thread, below how can done using approach described.
note _initializetask = background()
still asynchronous operation, despite it's taking place on ui thread. you won't able make synchronous without nested dispatcher event loop inside initializeform
(which bad idea because of implications ui re-entrancy).
that said, simplified version (no throttle or cancellation) may this:
task _initializetask; private void initializeform(list<nondependencyobject> mycollection) { action<nondependencyobject> dowork = (nondepobj) => { var dependencyobject = createdependencyobject(nondepobj); uicomponent.add(dependencyobject); // set binding on each dependencyobject , update progress bar ... }; func<task> background = async () => { foreach (var nondependencyobject in mycollection) { if (nondependencyobject.needstobeadded()) { dowork(nondependencyobject); await dispatcher.yield(dispatcherpriority.applicationidle); } } }; _initializetask = background(); }
Comments
Post a Comment