Closed quincycs closed 7 years ago
You use the dispatcher for the window whose UI thread you want to dispatch to. I'll make some changes to the snippet to make it clearer. Thanks for the report.
@oldnewthing also how do you know what UI thread you need to dispatch to? E.g. you use a bg thread, and then you want to notify the UI about a change. How do keep state so that you can map your bg thread to the correct UI thread?
You might also consider using DispatcherHelper, which is based on this snippet but adds support for more scenarios. (I will add a link to this from the snippet.)
You use the UI thread that corresponds to the UI you want to update.
I'm using helper classes similar to DispatcherHelper. DispatcherHelper doesn't help with knowing what is the right dispatcher to use. Either dispatcherHelper gets the main view's dispatcher or you give it a dispatcher.
Can you provide a sample in which you know the right dispatcher to use? Think of the scenario where you have an app that could duplicate itself with multiple windows. E.g. same page type across multiple windows. And you invoke a bg thread. How do you know which dispatcher to call to update the UI.
Maybe its something like this:
OnNavigatedTo(){
var dispatcher = this.Dispatcher;
ThreadPool.RunAsync(async () =>{
await Task.Delay(1000);//some network call for example.
dispatcher.RunAsync( () => {
//some UI update.
});
});
}
Honestly its a little frustrating that you have to keep this state yourself. In a big application where your bg thread is going thru multiple classes and methods, and you want to update the UI. You'd have to pass your dispatcher thru all the layers.
Kinda wish there was a way to add state onto the bg thread, like an ID to know what is the right dispatcher to use for a UI update.
If your code is running inside a DependencyObject
(like a Page
), it can ask for this.Dispatcher
. Otherwise, it has to get a dispatcher from somewhere.
But the operating system isn't psychic. It doesn't know what UI thread your background thread is doing work on behalf of.
if there was a way to give the bg thread the state what dispatcher it should use, then it would produce more modular code. E.g. some dictionary mapping bg thread ID to view ID.
It would be very hard for a single window application , to support multiple windows instances if there isn't a way to make this mapping.
E.g. I have a single window application just use DispatcherHelper.RunOnUI everywhere.
Now I make a new window where all pages could be reached in secondary window. Now you got to change all your DispatcherHelper references everywhere. Bless the soul needing to do multiple windows :)
Well, somebody needs to keep track of which UI thread you want to use. Suppose a background thread calls RunOnUI. But there are two views. What algorithm do you propose for the OS to use in order to decide which UI thread the task should run on? Yes, this is what happens when you have more than one thing. Code that assumes there's only one thing stops working.
Personally, I would keep UI-updating code close to the UI (for example, in the code-behind) so it can grab this.Dispatcher. Background code in non-UI classes could raise events that the UI can handle in order to call RunOnUI and get the effect back onto the right UI thread. This is a more modular approach where the background code is UI-agnostic.
Essentially, this is an architectural concern, and there are many different architectural possibilities. Many large-scale apps use MVVM frameworks that could provide some built-in solutions, for example. There is no single right way to do this, and it's trickier if you are starting from an existing code base that you don't want to have to re-architect.
@oldnewthing i think the algorithm is pretty simple. Pick the dispatcher that started the background thread in the first place. And make a convention in the app that you should only make a background thread from a UI thread.
@KarlErickson I'd like to know any framework or any library that addresses multi window UWP concerns. I don't think any exists. Yeah its more modular to have events work like that, but you still got to tie all your events together and make sure you release them properly.
Thanks guys for sticking with me in this issue. I realize this is pretty off topic, but this discussion I'm sure will be helpful to someone someday.
I don't know of any frameworks offhand that have support for multiple windows like that, but many of them have messaging systems that provide better decoupling for events.
Having a rule that only UI threads can create background threads means that we would have to deprecate Windows.Threading.ThreadPool
. Also, the system internally creates background threads triggered by external events, not by a UI thread. E.g. "the accelerometer detected motion" or "A network response was received."
thx.
@oldnewthing RE: threadPool. Not a suggestion for the platform to change, but rather an app developer to choose to make this rule in their code. For any of these external bg thread events, you could immediately invoke MainView's dispatcher or invoke all Views' dispatchers. Then as @KarlErickson is suggesting, (use a message subscription pattern) publish an event that listeners can handle. I implemented my own flavor of https://msdn.microsoft.com/en-us/library/ff921122.aspx . I'm sure the UWP community lib has something similar.
I think these external bg thread events aren't as super common as what the dispatcher is usually used for.
Ok, what do you think about this... use the following IDs so that the background thread knows what dispatcher to use later.
taskID = TaskScheduler.Current.Id uiID = ApplicationView.GetForCurrentView().Id
Anytime a view is created, initialize map[taskID] = uiID; Anytime a bg thread is created, initialize map[taskID] = uiID;
Anytime you need to get a dispatcher whether on UI thread or on BG thread, look at map[taskID] , then go thru the views to find the right view.
currentView = CoreApplication.Views.FirstOrDefault(x => x.CoreWindow != null && viewId == ApplicationView.GetApplicationViewIdForWindow(x.CoreWindow));
then
currentView.Dispatcher
That doesn't work because a single TaskScheduler can run multiple tasks. (Indeed, in the default configuration, there is only one TaskScheduler that runs all tasks for the entire process.) Each task may be running on behalf of a different UI thread. You could use Task.CurrentId
, but that won't help if you call it from a background thread not inside a task. (E.g., the event handler for a WinRT event.)
For event handlers, they need to be told which UI thread they are working on behalf of, because they can be invoked from any thread (and probably not as part of a task). The usual way of doing this is attaching a Dispatcher to the object that houses the event handler.
oh duh, TaskScheduler.Current.Id is always 1.
hm is there a way to set some value on the "thread" or "task" or whatever level so that we could get the value later? I'm thinking the value would just be the viewId. The problem is how to store basically this thread-level data.
If the event originates from the OS, then there is no task, and you don't control the thread. So even if you could put the viewId on the thread, that wouldn't help because the OS isn't using your thread.
Yeah, but I think if it's an event, then its pretty easy to route that to the UI that subscribed to it or route it to all windows to run a UI update.
What about the normal case - if the app goes thru threadpool to create bg thread. Is there a way to add some contextual data on that for it to know what viewId to use later?
Use lambda capture. Capture into the lambda the dispatcher or the viewId or whatever other information you need, and then consume the data in the lambda body.
yeah, then pass around that data via method parameters wherever you continue to need it.
Let's say you have an app with a resourcedictionary full of templates & brushes that is meant to be used across the app.
If you have multiple windows, is that wrong to use a central resource dictionary? B/c resource dictionary has a dispatcher on it, so my concern is that making another window use it will have undefined effects.
I tried it and sometimes I see the UI theme be wrong for a secondary window.
Just want to say thank you to @oldnewthing and @KarlErickson for brainstorming with me!
These aren't really questions about the task snippet. You can open a support case with our Developer Support team who can further help with these questions.
thank you
UI-thread-access-from-background-thread.md
How would you do this if you have a multi window application?