I'd like to load my viewmodel after a page has loaded but I can't seem to figure out how to do this.
Here's the viewModel methods where both methods inside LoadViewModel are long running async methods:
public async Task LoadViewModel()
{
await GetAllTiles();
await UpdateAllScenarioCardsAsync();
}
In the view I'm trying something like this:
private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var viewModel = DataContext as StatsViewModel;
if (viewModel != null)
{
var statsViewModel = viewModel;
await statsViewModel.LoadViewModel();
}
}
For some reason the LoadViewModel() method blocks the entire UI even if I remove the awaits for GetAllTiles() and UpdateAllScenarioCardsAsync()... The NavigationHelper_LoadState method is run before the page is loaded so I've tried registering LoadViewModel() to the Loaded event of the page but I can't seem to get it to work.
EDIT
Here is my UpdateAllScenarioCardsAsync() class. UpdateTotalTilesAsync() and UpdataTodayTilesAsync() have await statements inside the code as well but it still blocks the UI. I used closedScenario because I thought the issue could be closing over a variable over the wrong scope like answered in this question, but still now luck. I'm tempted to think it has something to do with the foreach loop because I have successfully done this elsewhere in my solution and it doesn't block the UI thread, but that code had no foreach loop.
private async Task UpdateAllScenarioCardsAsync()
{
IsPending = true;
try
{
// Load all of the scenario cards
foreach (var scenario in _scenariosList)
{
var closedScenario = scenario;
var data = new ScenarioDataCard(closedScenario);
await UpdateTotalTiles(data);
await UpdateTodayTestedTiles(data);
ScenarioDataCards.Add(data);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsPending = false;
}
}
You need to add an await inside the LoadState event handler. Otherwise it will simply block whilst waiting for the LoadViewModel() to return. Like this...
private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var viewModel = DataContext as StatsViewModel;
if (viewModel != null)
{
await viewModel.LoadViewModel();
}
}
I also assume that the GetAllTiles and UpdateAllScenarioCardsAsync methods are implemented so that they really do perform work on another thread so that they are not blocking the main user interface thread.
Related
I have a TreeView in a form, that is dock-filled to a groupbox. The problem to solve is, that there is an operation that is run on a Task, which loads data from a server application. When this is running, there should be a progress indicator displayed in the location of the TreeView. That is, it should be shown instead of the TreeView, and take its place fully. The following is what the code for this looks like:
private async void preload_data(object sender, System.EventArgs args)
{
try
{
// the treeView should be disabled/invisible at this point
// make the CircularProgressBar enabled/visible
// get the information from the server
await Configuration.network.fetch_stuff();
}
catch (Exception ex)
{
// something bad happened
}
finally
{
// whatever happened, the treeView should be back
}
}
The CircularProgressBar (a third-party control) should appear as in the code above, and should replace the TreeView. It should fill the exact same space as the TreeView would, which is dock-filled. Below is a screenshot of this:
This form and all its controls have been designed in the designer, and I don't want to do it there, I want to do it programmatically. What is the best way to go about this? I have looked at examples of Controls.Remove() and Controls.Add(), but it's not clear if that fits this purpose.
It is quite common to change the visual output while actions are running, like you do. Think of disabling buttons, to discourage operators to press the button again, or show something visually, to inform operators about the progress.
For simplicity, without the try-catch
private async Task PreloadDataAsync()
{
this.ShowFetchingData(true);
// start fetching data, do not await:
var taskFetchData = Configuration.network.fetch_stuff();
// while taskFetchData not completed, await some time
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
this.ShowFetchingData(false);
}
private void ShowFetchindData(bool show)
{
// disable/enable certain buttons, menu items, show progressbar?
this.ButtonFetchData.Enabled = !show;
this.MenuFetchData.Enabled = !show;
this.ProgressBarFetchData.Visible = show;
}
private bool IsFetchingData => this.ProgressBarFetchData.Visible;
private void ShowProgress(int progress)
{
this.ProgressBarFetchData.Position = progress;
}
For simplicity, I've left out checks for the position in the progress bar, but you get the gist.
Usage:
private async void OnButtonFetchData(object sender, EventArgs e)
{
await this.PreloadDataAsync();
}
Room for improvement
The problem with this is that there is no timeout at all: if FetchStuff does not complete, you are in an endless wait. The method that microsoft proposes is the use of a CancellationToken. Almost every async method has an overload with a CancellationToken. Consider creating one yourself:
// existing method:
private async Task<MyData> fetch_Stuff()
{
await this.fetch_stuff(CancellationToken.None);
}
// added method with CancellationToken
private async Task<MyData> fetch_Stuff(CancellationToken token)
{
// Call async function overloads with the token,
// Regularly check if cancellation requested
while (!token.IsCancellationRequested)
{
... // fetch some more data, without waiting too long
}
}
Instead of IsCancellationRequested, consider to throw an exception: ThrowIfCancellationRequested.
Usage:
private async Task PreloadDataAsync()
{
// preloading should be finished within 30 seconds
// let the cancellationTokenSource request cancel after 30 seconds
TimeSpan maxPreloadTime = TimeSpan.FromSeconds(30);
using (var cancellationTokenSource = new CancellationTokenSource(maxPreloadTime))
{
await PreloadDataAsync(cancellationTokenSource.Token);
}
}
The overload with CancellationToken:
private async Task PreloadDataAsync(CancellationToken token)
{
this.ShowFetchingData(true);
// execute code similar to above, use overloads that accept token:
try
{
var taskFetchData = Configuration.network.fetch_stuff(token);
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
token.ThrowIfCancellationRequested();
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime, token);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
}
catch (TaskCancelledException exc)
{
this.ReportPreloadTimeout();
}
finally
{
this.ShowFetchingData(false);
}
}
Or if you want a button that cancels the task:
private CancellationTokenSource cancellationTokenSource = null;
private book IsPreloading => this.CancellationTokenSource != null;
private async Task StartStopPreload()
{
if (!this.IsPreloading)
StartPreload();
else
CancelPreload();
}
private async Task StartPreload()
{
// preload not started yet; start it without timeout;
try
{
this.cancellationTokenSource = new CancellationTokenSource();
await PreloadDataAsync(this.cancellationTokenSource.Token);
}
catch (TaskCancelledException exc)
{
this.ReportPreloadCancelled();
}
finally
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
}
}
The method where operators can stop preloading:
private async void StopPreload()
{
this.cancellationTokenSource.Cancel();
// the method that created this source will Dispose it and assign null
}
All you have to do is create buttons / menu items to start / stop preloading
Solved using Visible properties of controls.
I have a problem with application freezes for a few seconds.
I loading data from XML file and deserialize to MyList.
public List<My20FieldsDataRecord> MyList;
...
void ShowDataInThread()
{
MyGrid.DataContext = MyList;
}
public void ShowDane(bool inThread)
{
if (inThread)
{
Thread thr = new Thread(ShowDataInThread);
thr.Start();
}
else
{
ShowDataInThread();
}
}
if inThread = false everything work fine, but application not responding for a 2-3 seconds.
When inThread = true application crash.
I want do this in thread, but i was not able to understand that how it works from examples on internet. I'll be very grateful for your help, becouse i have no idea how to do that.
Since Microsoft introduced the async / wait approach for .NET Framework programming in .NET 4.5, the code for async methods is a lot.
You can not find any async async with as example as such as type:
private async void button1_Click(object sender, EventArgs e)
{
string result = await AnMethodAsync();
textBox1.Text += result;
}
private Task<string> AnMethodAsync()
{
//Do somethine async
}
And you think this is done, the function will run async do not have to worry about hanging thead anymore, too strong.
But the problem is not so simple.
Now try to put in the AnMethodAsync () function the following code:
Thread.Sleep(5000);
return Task.FromResult("HoanHT");
Run the code above and when you press button1, the UI will hang stiff for 5s.
What the hell, I have applied async / await properly that the UI is still hanging.
After a brief look at the problem: In the AnMethodAsync function does not create any other task on the other thread. The consequence is that asyn away, but it still runs on UI thread -> UI freeze.
Then fix it, there are two ways:
Method 1: Create a new task and executable:
return Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
return "HoanHT";
});
Method 2: Use Task.Delay () instead of Thread.Sleep ()
private async Task<string> AnMethodAsync()
{
await Task.Delay(5000);
return "HoanHT";
}
This is the way I usually do with the asynchronous problem, plus one more way is to use ConfigureAwait () for the Task but will cause minor problems.
Here is a way that I've found to load data for a datagrid in the background while not blocking your UI.
First, create a lock object, and enable collection synchronization, then actually load the data on a background thread using Task.Run():
private readonly object _myDataLock = new object();
private FastObservableCollection<My20FieldsDataRecord> MyList = new FastObservableCollection<My20FieldsDataRecord>();
private CollectionViewSource MyListCollectionView = new CollectionViewSource();
public MyViewModelConstructor() : base()
{
// Other ctor code
// ...
// assign the data source of the collection views
MyListCollectionView.Source = MyList;
// Setup synchronization
BindingOperations.EnableCollectionSynchronization(MyList, _myDataLock);
}
private async void LoadMyList()
{
// load the list
await Task.Run(async () =>
{
MyList.ReplaceAll(await MyRepository.LoadMyList());
}
);
}
Then in your repository you could write:
public virtual async Task<IList<My20FieldsDataRecord>> LoadMyList()
{
var results = await this.DataContext.TwentyFieldDataRecords
.OrderByDescending(x => x.MyDate).ToListAsync().ConfigureAwait(false);
return results;
}
Then you could bind in your associated view like this:
<controls:DataGrid Name="MyListDataGrid" Grid.Row="1"
....
ItemsSource="{Binding MyListCollectionView.View}"
... >
For details, please see:
https://blog.stephencleary.com/2014/04/a-tour-of-task-part-0-overview.html
http://blog.stephencleary.com/2012/02/async-and-await.html#avoiding-context
https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.7);k(DevLang-csharp)&rd=true
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
I am rather new to multithreading and the likes and still trying to wrap my head around the whole thing.
I have the following scenario (simplified):
public partial class Form1 : Form
{
private AppLogic logic = new AppLogic();
private void GetData()
{
var dataSetNames = logic.GetDataSetNames();
foreach (var dataSetName in dataSetNames)
{
var page = new TabPage();
var dgv = new DataGridView { Dock = DockStyle.Fill, DataSource = logic.GetDataSet(dataSetName) }
page.Controls.Add(dgv);
tabControl1.TabPages.Add(page)
}
}
private void GetMeSomeDataToolStripMenuItem_Click(object sender, EventArgs e)
{
GetData();
}
/* ... */
}
That's fancy and all but since the part where logic gathers data takes a few seconds, the Form will always freeze. I don't want that so I am currently trying to get rid of that. What I tried was changing the GetData() call a bit:
Task<TabControl> t = Task<TabControl>.Factory.StartNew(GetData);
tabControl1 = t.Result;
Of course I adjusted GetData() accordingly so it now returns a new TabControl instead of accessing the Form directly.
This didn't improve my situation at all, though, which is probably because accessing the Result Property of a Task forces the accessing Task to wait for completion.
So I am currently looking for a different way to do this but I can't come up with anything.
I guess the best way would be to use ContinueWith:
t.ContinueWith(formerTask => {
if (formerTask.IsFaulted) return;
var x = formerTask.Result;
// do whatever but use Invoke if necessary
})
This starts a task to process the result after your task has completed.
Another way to deal with this is to make the eventhandler async:
private async void GetMeSomeDataToolStripMenuItem_Click(object sender, EventArgs e)
{
TabControl t = await Task<TabControl>.Run(GetData);
tabControl1 = t;
}
Note that the async void pattern should generaly be avoided but is Ok for eventhandlers.
This approach is much easier when you need to update your GUI with the returned results. Creating a TabControl on another thread is highly suspect though. You should separate the data-getting and the control-creating. The latter action should be done on the main Thread.
This will unfreeze your GUI but to make this really efficient the GetData method should be made async, and the task.Run replaced by awaiting an I/O method.
Alright, after some work I found out dryman's answer wasn't optimal for me. This is what I did:
private async void GetData()
{
tabControl1.TabPages.Clear();
Task<List<string>> dataSetNames = Task<List<string>>.Factory.StartNew(logic.GetDataSetNames);
await dataSetNames;
foreach (var dataSetName in dataSetNames.Result)
{
Task<DataTable> sourceTable = Task<DataTable>.Factory.StartNew(() => logic.GetDataSet(dataSetName));
TabPage page = new TabPage { Name = dataSetName }
DataGridView dgv = new DataGridView { Dock = DockStyle.Fill }
dgv.DataSource = await sourceTable;
page.Controls.Add(dgv);
tabControl1.TabPages.Add(page);
}
}
This even makes my code more DRY, since I don't have to write
Task<TabControl> t = Task<TabControl>.Factory.StartNew(GetData);
everytime I want to call GetData().
TL;DR: use await threadName; before accessing the Result-property of a thread if you don't want to block your accessing thread.
I have MVVM(Prism) Windows Store app and I don't understand how to do asynchronous serialization/deserialization using Json.NET library(Version 6.0.4) in ViewModel layer.
I have method which is bound to GridView's ItemClick event:
public async void GridViewClick(object sender, ItemClickEventArgs parameter)
{
if (App.IsInternet())
{
if (parameter != null)
_navigationService.Navigate("AnimeDetails",
await Task.Run(() => JsonConvert.SerializeObject(parameter.ClickedItem)));
}
else
{
new MessageDialog(ResourceController.GetTranslation("MainPage_FeatureUnavaliableOffline")).ShowAsync();
}
}
This method throws me following Exception:
The application called an interface that was marshalled for a different thread. (Исключение из HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I tried to use Dispatcher, but it didn't help me(similar wrong thread exception).
public async void GridViewClick(object sender, ItemClickEventArgs parameter)
{
if (App.IsInternet())
{
var serializedItem = string.Empty;
await
Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
await Task.Run(() => serializedItem = JsonConvert.SerializeObject(parameter.ClickedItem)));
if (parameter != null)
_navigationService.Navigate("AnimeDetails", serializedItem);
}
else
{
new MessageDialog(ResourceController.GetTranslation("MainPage_FeatureUnavaliableOffline")).ShowAsync();
}
}
Could anybody please explain me, how to do serialization/deserialization correctly?
Usually, serialization is fast enough you can just do it directly without messing with background threads at all:
if (parameter != null)
_navigationService.Navigate("AnimeDetails",
JsonConvert.SerializeObject(parameter.ClickedItem));
However, if your objects are really huge and you're sure you want to use a background thread, then you just have to do any UI access (i.e., ItemClickEventArgs.ClickedItem) on the UI thread and serialization on the background thread:
if (parameter != null)
{
var item = parameter.ClickedItem;
_navigationService.Navigate("AnimeDetails",
await Task.Run(() => JsonConvert.SerializeObject(item)));
}
Original message below. Let me try and explain with more details why I am asking for this.
I have a page that listens to the Share charm request:
void Page_Loaded(object sender, RoutedEventArgs e)
{
m_transferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.GetForCurrentView();
m_transferManager.DataRequested += TransferManager_DataRequested;
}
When the event fires (TransferManager_DataRequested) it does not fire on the UI thread:
void TransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var data = args.Request.Data;
// More related stuff omitted - not important.
data.SetDataProvider(StandardDataFormats.Bitmap, GetImage_DelayRenderer);
}
Now, when GetImage_DelayRender is called, it also does not get called on the UI thread. However, in it, I need to do a bunch of UI related things. Specifically, I need to call a method that only works on the UI (it's a method I use elsewhere and I want to reuse it's logic). The method is called GetImageAsync and it needs to run on the UI because it does a bunch of interactions with WriteableBitmap. It also does a bunch of async operations (such as writing to stream etc) which is why it's async. I block the UI on GetImageAsync() for as short a time as I can.
Here's what GetImage_DelayRender looks like:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
await Dispatcher.RunTask(async () => // RunTask() is an extension method - described in the original question below.
{
try
{
var bitmapStream = await GetImageAsync();
request.SetData(RandomAccessStreamReference.CreateFromStream(bitmapStream));
}
catch
{
}
});
deferral.Complete();
}
What I want to know is, what is the most correct way to achieve the call to Dispatcher.RunTask() above (which is my hack extension method).
----- START ORIGINAL MESSAGE -------
Say I have the following task:
private async Task SomeTask()
{
await Task.Delay(1000);
// Do some UI and other stuff that may also be async
}
Edit (Clarification): I do not want to block the UI. The task I want to execute (even in the example, if you read it) WILL NOT block the UI. I just want the task to run in the context of the UI for it's synchronous portions.
I want to run this on code on the UI thread as an Async operation. Dispatcher.RunXXX() methods take an action, which means they will run the action and notify you when they are done. That's not good enough. I need the entire task to run on the UI thread (as it would have executed had I run it from the UI thread) and then, when done, to notify me back.
The only way I could think of, is to use the Dispatcher.RunXXX() methods to execute an anon delegate that sets a local variable in my method to the task and then awaits that...
public async static Task RunTask(this CoreDispatcher dispatcher, Func<Task> taskGiver)
{
Task task = null;
await dispatcher.RunAsync(() => task = taskGiver());
await task;
}
This looks pretty damn ugly. Is there a better way of doing it?
Edit2: Guys - read this code - if I execute the first code block above using the RunTask() hack I have, IT WILL NOT BLOCK THE UI on the Task.Delay()...
I want to run this on code on the UI thread as an Async operation.
Then just run it:
async void MyEventHandler(object sender, ...)
{
await SomeTask();
}
Update:
I'm not sure this is a "legal" operation, but you can schedule that method to run on the UI thread by capturing the CoreDispatcher while the UI is active and later calling RunAsync:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
Task task = null;
await coreDispatcher.RunAsync(() => { task = SomeTask(); });
await task;
deferral.Complete();
}
I don't have time to do a complete solution, so hopefully you will still find this useful...
First, as others have pointed out, you cannot run something on the UI thread and not have it block the UI thread. End of discussion. What you are saying you need is something to run on a non-UI thread and periodically notify the UI thread that there are updates that need to be processed.
To accomplish this, you need something like this...
public class LongTask
{
public event EventHandler MyEvent;
public void Execute()
{
var task = Task.Factory.StartNew(() =>
{
while (true)
{
// condition met to notify UI
if (MyEvent != null)
MyEvent(this, null);
}
});
}
}
In your UI then, do something like...
private void button_Click(object sender, RoutedEventArgs e)
{
var test = new LongTask();
test.MyEvent += test_MyEvent;
test.Execute();
}
void test_MyEvent(object sender, EventArgs e)
{
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
test.Text += " bang ";
});
You could obviously implement this in a much cleaner fashion using something like MVVM, but this is the basic idea.
}
I've done it like this:
public static Task<string> GetResultAsync()
{
return Task<string>.Factory.StartNew(() => GetResultSync());
}
In UI:
private async void test()
{
string result = await GetResultAsync();
// update UI no problem
textbox.Text = result;
}