Uwp Update UI from Task

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, use CoreDispatcher::RunAsync. Always set the priority to
CoreDispatcherPriority::Normal or CoreDispatcherPriority::Low, and
ensure that any chained callbacks also use
CoreDispatcherPriority::Normal or CoreDispatcherPriority::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



Leave a reply



Submit