UWP update UI from Task
I also found another possibility how to update the UI - at this moment I am updating progress bar by this way, but found you can pass other variables..
var progress = new Progress<int>(i => _progress = i);
progress.ProgressChanged += Pr_ProgressChanged;
then event:
private void Pr_ProgressChanged(object sender, int e)
{
progressBar1.Value = progressBar1.Value + 1;
}
Method with parametrs:
public async Task GetWebServers(IProgress<int> progress) {
//your code
}
Calling:
await Task.WhenAll(workers.Select(webServer => webServer.GetWebServers(progress))
);
UWP Update UI From Async Worker
It is key to understand that async/await
does not directly say the awaited code will run on a different thread. When you do await GetDataAsync(pickedFile);
the execution enters the GetDataAsync
method still on the UI thread and continues there until await file.OpenStreamForReadAsync()
is reached - and this is the only operation that will actually run asynchronously on a different thread (as file.OpenStreamForReadAsync
is actually implemented this way).
However, once OpenStreamForReadAsync
is completed (which will be really quick), await
makes sure the execution returns to the same thread it started on - which means UI thread. So the actual expensive part of your code (reading the file in while
) runs on UI thread.
You could marginally improve this by using reader.ReadLineAsync
, but still, you will be returning to UI thread after each await
.
ConfigureAwait(false)
The first trick you want to introduce to resolve this problem is ConfigureAwait(false)
.
Calling this on an asynchronous call tells the runtime that the execution does not have to return to the thread that originally called the asynchronous method - hence this can avoid returning execution to the UI thread. Great place to put it in your case is OpenStreamForReadAsync
and ReadLineAsync
calls:
public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();
using (Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false))
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = await reader.ReadLineAsync().ConfigureAwait(false);
// Does its parsing here, and constructs a single DataRecord …
recordsList.Add(dataRecord);
// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}
return recordsList;
}
Dispatcher
Now you freed up your UI thread, but introduced yet another problem with the progress reporting. Because now MyStorageWrapper.RaiseMyEvent(recordsList.Count)
runs on a different thread, you cannot update the UI in the UpdateUI
method directly, as accessing UI elements from non-UI thread throws synchronization exception. Instead, you must use UI thread Dispatcher
to make sure the code runs on the right thread.
In the constructor get reference to the UI thread Dispatcher
:
private CoreDispatcher _dispatcher;
public MyPage()
{
this.InitializeComponent();
_dispatcher = Window.Current.Dispatcher;
...
}
Reason to do it ahead is that Window.Current
is again accessible only from the UI thread, but the page constructor definitely runs there, so it is the ideal place to use.
Now rewrite UpdateUI
as follows
private async void UpdateUI(long lineCount)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;
this.LastUpdate = now;
}
});
}
Correct way to access UI from background thread in UWP
The VieModel pulls data from the model into it's ObservableCollecton via a subscribed event.
You mention both "pull" and "event". Events are by nature "push" systems - the event is pushed to your code. However, there are some systems that produce asynchronous results via an event, so I assume that is what you're dealing with here since you specify the data is "pulled".
If this is correct, then the best solution is to first write a wrapper for the event-based asynchrony so it becomes task-based asynchrony. If you have a data service that looks like this:
class MyDataService
{
public void DownloadData();
public event MyDataArrivedEventHandler MyDataArrived;
}
then the wrapper would look something like this:
public static Task<MyData> GetMyDataAsync(this MyDataService service)
{
var tcs = new TaskCompletionSource<MyData>();
MyDataArrivedEventHandler handler = null;
handler = (s,e) =>
{
service.MyDataArrived -= handler;
if (e.Error != null)
tcs.TrySetException(e.Error);
else
tcs.TrySetResult(e.Data);
};
service.MyDataArrived += handler;
service.DownloadData();
return tcs.Task;
}
Once you have a Task-based asynchronous pattern method, then consuming it and updating your viewmodel is straightforward:
// From UI thread.
var data = await service.GetMyDataAsync();
viewModel.AddRange(data); // or whatever
This approach allows you to use the context-capturing nature of await
so that you don't have to do any thread transitions yourself.
How to Call a method in UI thread from another running thread in UWP?
The below should work.
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// update your UI here
Update(i)
});
updating UWP UI from PPL task continuation - warning C4451
These warnings from the compiler do not mean that you are doing something wrong, it means that you may be doing something wrong.
1> ... \appuwp1\mainpage.xaml.cpp(46): warning C4451: 'AppUwp1::MainPage::{ctor}::<lambda_df2e69e2b6fe4b1dfba3f26ad0398a3e>::myThread': Usage of ref class 'Windows::UI::Core::CoreWindow' inside this context can lead to invalid marshaling of object across contexts
1> ... \appuwp1\mainpage.xaml.cpp(46): note: Consider using 'Platform::Agile<Windows::UI::Core::CoreWindow>' instead
CoreWindow Class which has the following note:
This class is not agile, which means that you need to consider its
threading model and marshaling behavior. For more info, see Threading
and Marshaling
(C++/CX).
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model,
objects are hosted in different apartments, depending on how they
handle their synchronization. Thread-safe objects are hosted in the
multi-threaded apartment (MTA). Objects that must be accessed by a
single thread are hosted in a single-threaded apartment (STA).In an app that has a UI, the ASTA (Application STA) thread is
responsible for pumping window messages and is the only thread in the
process that can update the STA-hosted UI controls. This has two
consequences. First, to enable the app to remain responsive, all
CPU-intensive and I/O operations should not be run on the ASTA thread.
Second, results that come from background threads must be marshaled
back to the ASTA to update the UI. In a C++ Windows 8.x Store app,
MainPage and other XAML pages all run on the ATSA. Therefore, task
continuations that are declared on the ASTA are run there by default
so you can update controls directly in the continuation body. However,
if you nest a task in another task, any continuations on that nested
task run in the MTA. Therefore, you need to consider whether to
explicitly specify on what context these continuations run.
And yes, there is a slightly different way to write the source so as to eliminate the warning.
Eliminating the Warnings
If I modify the source of MainPage::MainPage() so that rather than use the Windows::UI::Core::CoreWindow ^
provided by CoreWindow::GetForCurrentThread();
within the worker thread started by concurrency::create_task()
I instead get the Dispatcher
from the UI thread itself then use the Dispatcher
object in the worker thread, I no longer get the warning. This is because Windows::UI::Core::CoreWindow
is not Agile so the thread that the CoreWindow
object comes from must be a consideration. However the Dispatcher
object is Agile.
The compiler warning had to do with accessing the Dispatcher
of the UI thread through the non-Agile CoreWindow
object from within the worker thread whereas with this version of getting the reference to the UI thread dispatcher within the UI thread and then using that Dispatcher
reference, the compiler is fine with that.
This version of the source code looks like:
MainPage::MainPage()
{
InitializeComponent();
myTextBlock->Text = "this is my text and some more text";
auto myDispatcher = CoreWindow::GetForCurrentThread()->Dispatcher;
concurrency::create_task([=]() {
Sleep(2000);
myDispatcher->RunAsync(
CoreDispatcherPriority::Normal,
ref new DispatchedHandler([=]()
{
myTextBlock->Text = "start of task";
// Do stuff on the UI Thread
}));
}).then([=]()
{
Sleep(5000);
for (int iCount = 0; iCount < 3; iCount++) {
myDispatcher->RunAsync(
CoreDispatcherPriority::Normal,
ref new DispatchedHandler([=]()
{
// Do stuff on the UI Thread
std::wstringstream ss;
ss << iCount << " text first";
myTextBlock->Text = ref new Platform::String(ss.str().c_str());
}) // close off the DispatchedHandler() lambda
); // close off the RunAsync()
Sleep(2000);
} // close off for loop
}).then([=]()
{
Sleep(5000);
myDispatcher->RunAsync(
CoreDispatcherPriority::Normal,
ref new DispatchedHandler([=]()
{
// Do stuff on the UI Thread
myTextBlock->Text = "this is my text and some more text after sleep after sleep again";
}));
});
}
CoreDispatcher.RunAsync(CoreDispatcherPriority, DispatchedHandler) Method has Remarks as follows:
If you are on a worker thread and want to schedule work on the UI
thread, useCoreDispatcher::RunAsync
. Always set the priority to
CoreDispatcherPriority::Normal
orCoreDispatcherPriority::Low
, and
ensure that any chained callbacks also use
CoreDispatcherPriority::Normal
orCoreDispatcherPriority::Low
.
Background on Threading and Async and Agile Methods
Much of the .NET functionality and Windows Runtime functionality as well as more and more general purpose functionality is being provided in the form of COM controls and functionality. Using the COM technology allows for the same functionality to be used by a wide variety of languages, platforms, and technologies.
However along with COM technology is a great deal of complexity which fortunately can be hidden to a great extent by encapsulating it with various language specific wrappers.
One consideration of COM technology is the idea of Apartments. The MSDN article Processes, Threads, and Apartments provides a somewhat technical introduction to the subject.
Creating Asynchronous Operations in C++ for Windows Store Apps
The Windows Runtime uses the COM threading model. In this model,
objects are hosted in different apartments, depending on how they
handle their synchronization. Thread-safe objects are hosted in the
multi-threaded apartment (MTA). Objects that must be accessed by a
single thread are hosted in a single-threaded apartment (STA).In an app that has a UI, the ASTA (Application STA) thread is
responsible for pumping window messages and is the only thread in the
process that can update the STA-hosted UI controls. This has two
consequences. First, to enable the app to remain responsive, all
CPU-intensive and I/O operations should not be run on the ASTA thread.
Second, results that come from background threads must be marshaled
back to the ASTA to update the UI. In a C++ Windows 8.x Store app,
MainPage and other XAML pages all run on the ATSA. Therefore, task
continuations that are declared on the ASTA are run there by default
so you can update controls directly in the continuation body. However,
if you nest a task in another task, any continuations on that nested
task run in the MTA. Therefore, you need to consider whether to
explicitly specify on what context these continuations run.
With the Windows Runtime the concept of Agile and non-Agile threads was introduced. The Microsoft Docs article Threading and Marshaling (C++/CX) provides an introduction for the C++ programmer.
In the vast majority of cases, instances of Windows Runtime classes,
like standard C++ objects, can be accessed from any thread. Such
classes are referred to as "agile". However, a small number of Windows
Runtime classes that ship with Windows are non-agile, and must be
consumed more like COM objects than standard C++ objects. You don't
need to be a COM expert to use non-agile classes, but you do need to
take into consideration the class's threading model and its marshaling
behavior. This article provides background and guidance for those rare
scenarios in which you need to consume an instance of a non-agile
class.
See also
Threading Model which discusses WPF threading models.
Historically, Windows allows UI elements to be accessed only by the
thread that created them. This means that a background thread in
charge of some long-running task cannot update a text box when it is
finished. Windows does this to ensure the integrity of UI components.
A list box could look strange if its contents were updated by a
background thread during painting.
A nice explanation on COM Threading Models from The Open Group
C# Updating UI from a multiple Tasks running in parallel
You can invoke a function back to the UI thread:
MethodInvoker mI = () => {
//this is from my code - it updates 3 textboxes and one progress bar.
//It's intended to show you how to insert different commands to be invoked -
//basically just like a method. Change these to do what you want separated by semi-colon
lbl_Bytes_Read.Text = io.kBytes_Read.ToString("N0");
lbl_Bytes_Total.Text = io.total_KB.ToString("N0");
lbl_Uncompressed_Bytes.Text = io.mem_Used.ToString("N0");
pgb_Load_Progress.Value = (int)pct;
};
BeginInvoke(mI);
To apply this to your needs, have your tasks update a class or a queue, and then empty it into the UI using a single BeginInvoke.
class UI_Update(){
public string TextBox1_Text {get;set;}
public int progressBar_Value = {get;set;}
//...
System.ComponentModel.BackgroundWorker updater = new System.ComponentModel.BackgroundWorker();
public void initializeBackgroundWorker(){
updater.DoWork += UI_Updater;
updater.RunWorkerAsync();
}
public void UI_Updater(object sender, DoWorkEventArgs e){
bool isRunning = true;
while(isRunning){
MethodInvoker mI = () => {
TextBox1.Text = TextBox1_Text;
myProgessBar.Value = progressBar.Value;
};
BeginInvoke(mI);
System.Threading.Thread.Sleep(1000);
}
}
}
PS - there may be some mis-spelling here. I have to leave like yesterday but I wanted to get my point across. I'll edit later.
EDIT for UWP, try
CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
});
in place of BeginInvoke;
Related Topics
Date Difference in Years Using C#
How to Check If Thread Finished Execution
Automatically Rename a File If It Already Exists in Windows Way
How to Disable Cascade Delete for Link Tables in Ef Code-First
Select Either a File or Folder from the Same Dialog in .Net
How to Programmatically Change Printer Settings with the Webbrowser Control
Getting Hwnd Off of Corewindow Object in Uwp
How to Run External Program via a C# Program
How to Get Around Lack of Covariance with Ireadonlydictionary
Count Number of Mondays in a Given Date Range
Interaction Between Webbrowser Control and Windows Forms
How to Get the Colour of a Pixel at X,Y Using C#
How to Add the Same Column to All Entities in Ef Core