How to process only last Task and discard previous in c# - c#

I have a WPF application. Window includes datagrid for showing data and button for searching them. In viewmodel there is a method for loading data to the datagrid.
public List<MyObject> DataGridData { get; set; }
public void LoadDataToDatagrid(object obj)
{
Task.Run(() =>
{
DataGridData = new List<MyObject>(repository.GetData());
});
}
Everytime, when i click the button to load the data, the data is loaded to datagrid, but it takes a long time. So when i click the button more than once, the first data are loaded. I will start working with them and then new data are loaded again.
How can I stop previous tasks and load data only for last click button?
Thanks for your answers

You could maybe disable the button until the content is loaded?
You would have to set your button disabled property to have a dependency on a bool in your ViewModel and then set it to true when you are adding to the datagrid and then set it to false when you are done.
To do this, I am assuming you can raise events of some kind of:
NotifyPropertyChanged();
In XAML you would do something like this:
<Button IsEnabled="{Binding CanClose}"/>
Where CanClose is a bool in your ViewModel. When you change the state of the bool, you have to raise some kind of event. You can also disable the button first, then start filling the datagrid and when finished, enable it again?

Solved:
List<CancellationTokenSource> tokens = new List<CancellationTokenSource>();
public List<MyObject> DataGridData { get; set; }
public void LoadDataToDatagrid(object obj)
{
foreach (var token in tokens)
token.Cancel();
CancellationTokenSource tokenSource = new CancellationTokenSource();
tokens.Add(tokenSource);
var task = Task.Run(() =>
{
return repository.GetData();
}, tokenSource.Token);
try
{
DataGridData = await task;
}
finally
{
tokenSource.Dispose();
tokens.Remove(tokenSource);
}
}
That code show only data from last method call withou freeze application.
Thanks for answer Dmitry Bychenko.

Related

I need to update a Blazor page to update whenever the query string changes

I have a Blazor component named NavBar.razor that displays a Radzen navigation menu with a list of tags. When the user clicks a tag (RadzenPanelMenuItem), the component OrderBrowser.razor is loaded into the page next to the menu. The query string lets OrderBrowser.razor know what tag was selected. (See the OnInitializedAsync method below.) The component loads the associated orders into a grid.
This works fine the first time the user clicks a tag, but when they click a different tag, the OnInitializedAsync method does not execute, even though the uri changes. So I added an event handler to force a reload when the uri changes. This works, but, for some reason, it seems to reload twice, resulting in an undesirable blink when it reloads the 2nd time.
Does anyone know a better way to do this? Thanks.
Code from NavBar.razor:
#foreach (var item in TagsAndCounts)
{
<Radzen.Blazor.RadzenPanelMenuItem
Text="#(item.Tag + " (" + item.Count + ")")"
Path="#("orders/browse?tag=" + item.Tag)" />
}
Order grid from OrderBrowser.razor:
<OrderGrid Data="#orders" AllowPaging="false" />
Code from OrderBrowser.razor:
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
NavManager.LocationChanged += NavManager_LocationChanged;
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("tag", out var tag))
{
orders = await orderService.GetOrdersForTagAsync(tag);
}
}
private void NavManager_LocationChanged(object sender, LocationChangedEventArgs e)
{
NavManager.NavigateTo(NavManager.Uri, forceLoad: true);
}
Use this event
[Parameter]
[SupplyParameterFromQuery]
public string? Page { get; set; } = "0";
protected override void OnParametersSet()
{
//Fire your Code her
}
When you go from: yourUrl/tag/tagname1 and click to link yourUrl/tag/tagname2 it does not fire OnInitializedAsync, because new page was not created. It is intended and correct behavior. But bit confusing.
You can leverage new, with .net6 introduced, capability of SupplyFromQueryParameter attribute, which will change its value based on query string.
[Parameter, SupplyParameterFromQuery(Name = "tag")] public string Tag { get; set; } = "";
Now you can do the magic inside seeter of the Tag property. Or, if you need to call async method inside setter (which is not a good practice), you can use OnParametersSet method. It is called right after parameters has been changed. So you also need a mechanism to check if tag parameter has been changed (because it is called every time *some* parameter has been changed )
bool tagUpdated = true;
string _tag ="";
[Parameter,SupplyParameterFromQuery] public string Tag { get => _tag; set { if (value != _tag) { _tag = value; tagUpdated = true; } } }
protected override async Task OnParametersSetAsync()
{
if (tagUpdated)
{
tagUpdated = false;
await YourAsyncCallAndOtherMagic();
}
}
Note, that SupplyFromQueryParameter works only on pages (.razor components with #page directive)
(still not the most beautiful solution I guess. Open for suggestions...)
I found a solution. It's still a bit of a hack, but it seems to work. If anybody has any better solutions, please let me know. I just changed the event handler to get the new URL and update the page, instead of forcing a reload.
private async void NavManager_LocationChanged(object sender, LocationChangedEventArgs e)
{
orders = await orderService.GetOrdersForTagAsync(tag);
var index = NavManager.Uri.LastIndexOf("/");
tag = NavManager.Uri.Substring(index + 1);
if (tag != "open_orders")
{
StateHasChanged();
}
}

Action after ActivateItem in Caliburn.Micro

I have a question. I have button where i launch ActivateItem:
public void LoadTaskManagerPage()
{
this.ActivateItem(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (this.LoggedUser.GetUserTask() != null)
{
this.IsActiveTaskButtonVisible = Visibility.Visible;
this.NotifyOfPropertyChange(() => this.IsActiveTaskButtonVisible);
}
}
Is it possible to hang an app and go to if statement only, if ActivateItem ends?
How to wait for end of ActivateItem in Caliburn.Micro?
EDIT:
Trying something like:
public void LoadTaskManagerPage()
{
var taskManagerTask = Task.Run(() => this.ActivateItem(new TaskManagerViewModel(this.LoggedUser, this.repository)));
taskManagerTask.Wait();
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
this.IsActiveTaskButtonVisible = Visibility.Visible;
this.NotifyOfPropertyChange(() => this.IsActiveTaskButtonVisible);
}
}
With Tasks, but when i click on LoadTaskManagerPage() it wont show any window, application hangs forever
EDIT2
Based on my issue on github, i upgraded Caliburn to alpha 4.0:
public void LoadTaskManagerPage()
{
this.ActivateItemAsync(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
//logic
}
}
i changed ActiveItem() to ActiveItemAsync() but still, it hit if statement before active item will close. using async/await do the same
EDIT3
When i make async/await it still works the same, look at my view on the right. Its clicked, User Control not appear. In the same time (even when my User Control not show up) im hitting if statement too early. I want to hit it on User Control close.
You should await the ActivateItemAsync method. This means that your LoadTaskManagerPage() should return a Task and you should await this one as well:
public async Task LoadTaskManagerPageAsynnc()
{
await this.ActivateItemAsync(new TaskManagerViewModel(this.LoggedUser, this.repository));
if (!this.LoggedUser.GetUserTask().IsTaskTakenByUser())
{
//logic
}
}

"Only a single ContentDialog can be open at any time." error while opening another contentdialog

I am using Windows.UI.Xaml.Controls.ContentDialog to show a confirmation. And based on the response from the first dialog I would (or would not) show another dialog. But, when I am trying to open the second content dialog it throws : "Only a single ContentDialog can be open at any time." error. Even though in the UI, first dialog would be closed but somehow I am still not able to open the second dialog. Any idea?
I have created some code to handle this type of conundrum in my Apps:
public static class ContentDialogMaker
{
public static async void CreateContentDialog(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
public static async Task CreateContentDialogAsync(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
static async Task CreateDialog(ContentDialog Dialog, bool awaitPreviousDialog)
{
if (ActiveDialog != null)
{
if (awaitPreviousDialog)
{
await DialogAwaiter.Task;
DialogAwaiter = new TaskCompletionSource<bool>();
}
else ActiveDialog.Hide();
}
ActiveDialog = Dialog;
ActiveDialog.Closed += ActiveDialog_Closed;
await ActiveDialog.ShowAsync();
ActiveDialog.Closed -= ActiveDialog_Closed;
}
public static ContentDialog ActiveDialog;
static TaskCompletionSource<bool> DialogAwaiter = new TaskCompletionSource<bool>();
private static void ActiveDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args) { DialogAwaiter.SetResult(true); }
}
To use these Methods, you need to create the ContentDialog and its content in a variable, then pass the variable, and bool into the Method.
Use CreateContentDialogAsync(), if you require a callback in your app code, say if you have a button in your Dialog, and you want wait for a button press, and then get the value from the form in code after the dialog.
Use CreateContentDialog(), if you don't need to wait for the Dialog to complete in your UI Code.
Use awaitPreviousDialog to wait for the previous dialog to complete before showing the next Dialog, or set false, to remove the previous Dialog, then show the next Dialog, say, if you want to show an Error Box, or the next Dialog is more important.
Example:
await ContentDialogMaker.CreateContentDialogAsync(new ContentDialog
{
Title = "Warning",
Content = new TextBlock
{
Text = "Roaming Appdata Quota has been reached, if you are seeing this please let me know via feedback and bug reporting, this means that any further changes to data will not be synced across devices.",
TextWrapping = TextWrapping.Wrap
},
PrimaryButtonText = "OK"
}, awaitPreviousDialog: true);
William Bradley's approach above is good. Just to polish it up a bit, here is an extension method to submit and await the showing of a content dialog; the dialog will be shown after all the other content dialogs that have already been submitted. Note: by the time the user clicks through earlier backlogged dialogs you may no longer want to show the dialog that you have submitted; to indicate this you may pass a predicate that will be tested after the other dialogs have been dismissed.
static public class ContentDialogExtensions
{
static public async Task<ContentDialogResult> EnqueueAndShowIfAsync( this ContentDialog contentDialog, Func<bool> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
TaskCompletionSource<Null> previousDialogCompletion = null;
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
previousDialogCompletion = ContentDialogExtensions.PreviousDialogCompletion;
ContentDialogExtensions.PreviousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null) {
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate()) {
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
static private TaskCompletionSource<Null> PreviousDialogCompletion = null;
}
Another way might be to use a SemaphoreSlim(1,1).
"Only a single ContentDialog can be open at a time"
This statement is not entirely true. You can only ShowAsync one ContentDialog at a time. All you need to do is hide the current ContentDialog before opening another one. Then, after the "await ShowAsync" of the second ContentDailog, your simply call "var T = this.ShowAync()" to unhide it. Example:
public sealed partial class MyDialog2 : ContentDialog
{
...
}
public sealed partial class MyDialog1 : ContentDialog
{
...
private async void Button1_Click(object sender, RoutedEventArgs e)
{
// Hide MyDialog1
this.Hide();
// Show MyDialog2 from MyDialog1
var C = new MyDialog2();
await C.ShowAsync();
// Unhide MyDialog1
var T = ShowAsync();
}
}
I know this is slightly old, but one simpler solution instead of going through all this pain is to just register a callback for the ContentDialog_Closed event. By this point you can be sure the previous dialog has been closed, and can open your next dialog. :)
Only a single ContentDialog can be open at any time.
That is a fact. (I was really surprised, but just for a moment)
You can't have more than one at any time and it is more like guideline from Microsoft, because it's really messy to have multiple dialogs on top of each other filled with content.
Try to change your UX to display only one sophisticated ContentDialog and for all other messages use MessageDialog - it supports multiple buttons(only two for phones, but more on desktop) for user response but without Checkboxes or similar "smart"-content stuff.
In my case MessageDialogs were really helpful, but in some areas I used chained ContentDialogs but for that you must await the first one, and open second right after without any exceptions. In your case it seems like ContentDialog was not fully closed when you tried to open next one.
Hope it helps!
I like this answer https://stackoverflow.com/a/47986634/942855, this will allow us ot handle binding all events.
So extended it a little to check the multiple calls to show dialog.
private int _dialogDisplayCount;
private async void Logout_OnClick(object sender, RoutedEventArgs e)
{
try
{
_dialogDisplayCount++;
ContentDialog noWifiDialog = new ContentDialog
{
Title = "Logout",
Content = "Are you sure, you want to Logout?",
PrimaryButtonText = "Yes",
CloseButtonText = "No"
};
noWifiDialog.PrimaryButtonClick += ContentDialog_PrimaryButtonClick;
//await noWifiDialog.ShowAsync();
await noWifiDialog.EnqueueAndShowIfAsync(() => _dialogDisplayCount);
}
catch (Exception exception)
{
_rootPage.NotifyUser(exception.ToString(), NotifyType.DebugErrorMessage);
}
finally
{
_dialogDisplayCount = 0;
}
}
modified predicate
public class Null { private Null() { } }
public static class ContentDialogExtensions
{
public static async Task<ContentDialogResult> EnqueueAndShowIfAsync(this ContentDialog contentDialog, Func<int> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
var previousDialogCompletion = _previousDialogCompletion;
_previousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null)
{
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate() <=1)
{
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
private static TaskCompletionSource<Null> _previousDialogCompletion;
}

Waiting for a thread to finish in c# / Xamarin

I got a tricky thing here. I am developing an iOS App with Xamarin at the moment. (I think if you don't know about xamarin but c# you might know the answer as well - its an issue with threading basically!) I have a table with several entries, and when the user clicks one of the entries, it is required that i load data from a web service. This data is then displayed in the second table shown another view i push to. The problem is: In the exact moment the user clicks the table entry, the push segue to another view is triggered, the viewDidLoad() there is also triggered, in which i set the tables entries. But now sometimes it occurs that the async networking task from the previous view didn't finish, which leaves the table empty. To sum it up: I need to trigger a Table.Reload() on the View i push to, after the asynchronous networking request has done its work. How do i do that? I am really stuck here. Here are some details of my Code:
First View Controller
public async override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender)
{
//Sends the titles, subtitles, and the Headline to the DetailView
base.PrepareForSegue (segue, sender);
...
Task<String[,]> getJsonDataTask = SearchRequest.searchRequest (text, "Calendar");
arrayContent = await getJsonDataTask;
...
for (int i = 0; i < 50; i++) {
tableItems.Add(arrayContent [1, i]);
tableSubtitles.Add( arrayContent [5,i]);
Dates.Add(tableItems[i]);
Descriptions.Add (tableSubtitles [i]);
}
var calendarDetailController = segue.DestinationViewController as CalendarDetailVController;
if (calendarDetailController != null) {
Console.WriteLine ("preparesegue"+Dates.Count);
calendarDetailController.Dates = Dates;
calendarDetailController.Descriptions = Descriptions;
calendarDetailController.TableTitle = text;
calendarDetailController.id = id;
}
So here i am starting an async Thread (it needs to by ansync. cause its a network request)
And i am transferring the data to the view controller i push to.
In the
Secound View Controller
I just fetch the given data like
public List<String> Dates { get; set; }
public CalendarViewController (IntPtr handle) : base (handle)
{
Dates = new List<String> ();
}
And then i reload the tables data in ViewDidLoad();
public override async void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
//as the resource loading is async. whe need to refresh table here
TableView.ReloadData ();
}
Sometimes, if this ViewDidLoad() gets triggered, AFTER the async task finished, this works. But i need to trigger the Reload when the Data has fully arrived. Do you understand my problem? Thanks for your help.
How about you pass your task instead of the result of the task?
Change:
public List<String> Dates { get; set; }
To:
public Task<String[,]> DatesTask{get;set;}
And you execute the task in the viewDidAppaer of your second viewcontroller:
public override async void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
//as the resource loading is async. whe need to refresh table here
Task.Run (async delegate {
await DatesTask().ContinueWith(() => {
InvokeOnMainThread (delegate {
TableView.ReloadData ();
});
});
});
}
I would not recommend to make a method like ViewDidAppaer wait, instead i would let a task handle the execution of your method, and once it is done, make your tableview refresh. That way you will not freeze your userinterface while executing your task!
Or if you really want to execute your task in your first viewcontroller, you should execute your task BEFORE performing PrepareForSegue .
If I understood your question correctly it seems that you only need to pass the async Task from the view that initiated the call to your second view that needs to await it to finish, nothing fancy here.

WPF progressbar not showing

I set a WPF ProgressBar's Is indeterminate to True and Visibility to Hidden. In a event handler I am trying to make the ProgressBar visible while a ObservableCollection is being updated (and a ListView whose ItemsSource is the ObservableCollection). I hope DoEvents() from System.Windows.Forms.Application can make it visible but it does not.
I notice SetPBarHelper(() => { ..} ) usually finish much earlier than the ListView shows visual changes.
How do I make the ProgressBar Visible inside event handler codes ?
How to tell if my ListView is still being updated even though the ObserableCollection has finished adding items ?
<ProgressBar x:Name="GeneralProgressBar" Width="300" Height="15" IsIndeterminate="True" Visibility="Hidden"/>
private void SetPBar(bool isVisible)
{
if (isVisible)
GeneralProgressBar.Visibility = System.Windows.Visibility.Visible;
else
GeneralProgressBar.Visibility = System.Windows.Visibility.Hidden;
}
private void SetPBarHelper(Action handler)
{
SetPBar(true); // try to make ProgressBar visible
System.Windows.Forms.Application.DoEvents();
handler(); // use the event handling, which run database query
SetPBar(false); // try to make ProgressBar disappear
}
private void CommandForumImagesBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
SetPBarHelper(() =>
{
if (e.Parameter == null)
return;
var vm = e.Parameter as ForumViewModel;
if (vm != null)
{
}
});
}
public sealed class ImageGroupCollection : ObservableCollection<ImageGroup>
{
public ImageGroupCollection() : base() { }
public void Update(DateTime start, DateTime end)
{
ClearItems();
var list = MyDatabase.GetRecords(start, end);
if (list != null)
{
foreach (var g in list)
{
Add(g);
}
}
}
}
Your problem is simply because you are blocking the UI thread. You can't do that.
By the time the UI thread is running the message loop again you have set ProgressBar.Visible = false. The ProgressBar is never drawn.
Assuming you are using .net 4.5 You need to rewrite the code as follows.
private async Task SetPBarHelper(Action handler)
{
SetPBar(true); // try to make ProgressBar visible
System.Windows.Forms.Application.DoEvents();
await Task.Run(handler); // use the event handling, which run database query
SetPBar(false); // try to make ProgressBar disappear
}
But overall you need to return control of the UI thread back to the application loop as soon as possible to allow it to redraw the windows, and run the update on a WorkerThread.

Categories

Resources