Task.ContinueWith() is not working after the Task.Run() - c#

Im doing with the activity indicator of the xamarin.
I want to stop the indicator after the product is get.
protected override void OnAppearing()
{
base.OnAppearing();
if (!_appeared) // Avoid repeat loding
{
activity.IsEnabled = true;
activity.IsRunning = true;
activity.IsVisible = true;
var task = Task.Run(() =>
{
ProductViewData productViewData = new ProductViewData();
products = productViewData.GetProductList("10");
count = 10;
productListView.ItemsSource = products;
});
task.ContinueWith(t =>
{
activity.IsEnabled = false;
activity.IsRunning = false;
activity.IsVisible = false;
});
_appeared = true;
}
}
Thanks for any help.

Code like this is a lot easier with async/await:
protected override async void OnAppearing()
{
base.OnAppearing();
if (!_appeared) // Avoid repeat loding
{
_appeared = true;
activity.IsEnabled = true;
activity.IsRunning = true;
activity.IsVisible = true;
var products = await Task.Run(() =>
{
ProductViewData productViewData = new ProductViewData();
return productViewData.GetProductList("10");
});
productListView.ItemsSource = products;
activity.IsEnabled = false;
activity.IsRunning = false;
activity.IsVisible = false;
}
}
Although the code is simple and readable, there's a lot going on behind the scenes, particularly around when this method returns, and what thread the code after the await is run on. It is well worth reading up on this.

I guess this is what you need.
var task = Task.Run(delegate { Console.WriteLine("Task started!"); })
.ContinueWith(delegate { Console.WriteLine("Task continued"); });
Just wrap what you wanna do as functions and call that functions in your task steps. For more information read here.
Edit:
Since an UI operation is the subject of this question, I thought this code from the page I linked would be more useful. This code utilizes UI and background scheduler.
private void Button1_Click(object sender, EventArgs e)
{
var backgroundScheduler = TaskScheduler.Default;
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(delegate { DoBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUI(); }, uiScheduler).
ContinueWith(delegate { DoAnotherBackgroundComputation(); },
backgroundScheduler).
ContinueWith(delegate { UpdateUIAgain(); }, uiScheduler);
}

Related

UI only partly responsive while running tasks and updating ui

i have the following ui -
For each line connection to crm should be tested. This is done in separate thread.
The test status of the connection to crm system is then updated in last column.
The problem is that the ui is only partly reponsive during threads run and updating of the ui, i.e.
i would like to click through the lines whilst updating.
Here is my code:
private async void btnTestAllConnections_Click(object sender, EventArgs e)
{
await TestConnectionsAsync();
}
private async Task TestConnectionsAsync()
{
try
{
int idxConn = columnLookup[ColumnIndex.Connection].Index;
if (lvInitParameters.Items.Count == 0)
return;
ManagedConnection connection = null;
btnTestAllConnections.Visible = false;
btnTestConnection.Visible = false;
panel2.Enabled = false;
panel3.Enabled = false;
tableLayoutPanel1.Enabled = false;
btnCancelTest.Visible = true;
List<Task> tasks = new List<Task>();
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
foreach (ListViewItem lvi in lvInitParameters.Items)
{
InitParamProxy currentProfile = (InitParamProxy)lvi.Tag;
lvi.SubItems[idxConn].Text = "Testing...";
Task<bool> result =null;
try
{
result = Task.Run(
() =>
{
try
{
connection = currentProfile.ManagedConnection;
return connection?.ConnectionSuccess ?? false;
}
catch (Exception ex)
{
// crm exception
return false;
}
}, token);
if (token.IsCancellationRequested)
{
Console.WriteLine("\nCancellation requested in continuation...\n");
token.ThrowIfCancellationRequested();
}
ListViewItem testItem =
items.Where(si => ((InitParamProxy)lvi.Tag).ProfileKey.Equals(((InitParamProxy)si.Tag).ProfileKey)).SingleOrDefault();
lvi.SubItems[idxConn].Text = (result.Result) ? "Success" : "Fail";
if (testItem != null)
testItem.SubItems[idxConn].Text = (result.Result) ? "Success" : "Fail";
}
catch
{
ListViewItem testItem =
items.Where(si => ((InitParamProxy)lvi.Tag).ProfileKey.Equals(((InitParamProxy)si.Tag).ProfileKey)).SingleOrDefault();
lvi.SubItems[idxConn].Text = "Canceled";
if (testItem != null)
testItem.SubItems[idxConn].Text = "Canceled";
}
tasks.Add(result);
}
Task.WaitAll(tasks.ToArray());
btnTestAllConnections.Visible = true;
btnTestConnection.Visible = true;
panel2.Enabled = true;
panel3.Enabled = true;
tableLayoutPanel1.Enabled = true;
btnCancelTest.Visible = false;
}
catch (Exception)
{
}
}
In the end of your method you have
Task.WaitAll(tasks.ToArray());
This will block until all tasks are done. You should instead use WhenAll
await Task.WhenAll(tasks.ToArray());
You are also using result.Result in several places, and this also blocks. This should be replaced by awaiting the task, i.e. await result

Doesn't run RunOnUiThread after running some Task with ContinueWith or delegate

I want to change my UI elements(hide progressbar and assign data for listview) in UI-thread after running Task(obtaining some data from API). Problem with API I haven't find, data returning in all cases. Sometimes executing of code(in RunOnUiThread)after running Task becomes unreponsive, especially when I run code in Debug mode without any breakpoints. When I catch breakpoint before calling Task and continue running, all works fine.
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_userData = ServiceLocator.GetService<IAuthService>().GetUserData();
_wallsViewPresenter = ViewPresenterHelper.CreateViewPresenter<WallViewPresenter, IWallView, WallActivity>(this);
_listView = FindViewById<ListView>(Resource.Id.postList);
progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar1);
Task.Run(async () => {
profile = await _wallsViewPresenter.GetProfile(int.Parse(_userData.Profile));
WallModel wall = SerializationService.DeSerialize<WallModel>(profile.Wall);
_posts = (List<PostModel>) (wall.Posts.ToList());
}).ContinueWith(ar =>
{
RunOnUiThread(() => {
progressBar.Visibility = ViewStates.Gone;
_postListAdapter = new PostListAdapter(this, _posts);
_listView.Adapter = _postListAdapter;
SetListViewHeader();
_listView.AddHeaderView(_header);
FindViewById<TextView>(Resource.Id.details).Text = profile.Name;
});
});
}
Version with delegate:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_userData = ServiceLocator.GetService<IAuthService>().GetUserData();
_wallsViewPresenter = ViewPresenterHelper.CreateViewPresenter<WallViewPresenter, IWallView, WallActivity>(this);
_listView = FindViewById<ListView>(Resource.Id.postList);
progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar1);
Task.Run(async () => {
profile = await _wallsViewPresenter.GetProfile(int.Parse(_userData.Profile));
WallModel wall = SerializationService.DeSerialize<WallModel>(profile.Wall);
_posts = (List<PostModel>) (wall.Posts.ToList());
if (_posts.Count != 0)
{
DataPopulated?.Invoke(this, true);
}
});
DataPopulated += (sender, e) => {
RunOnUiThread(() => {
progressBar.Visibility = ViewStates.Gone;
_postListAdapter = new PostListAdapter(this, _posts);
_listView.Adapter = _postListAdapter;
SetListViewHeader();
_listView.AddHeaderView(_header);
FindViewById<TextView>(Resource.Id.details).Text = profile.Name;
});
};
}
ContinueWith is a very dangerous, low-level API; and should not be used here. Also, with async/await, there's no need for RunOnUIThread.
It's also likely that the Task.Run is unnecessary, since the method you're calling is already asynchronous.
Simplified version:
protected override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_userData = ServiceLocator.GetService<IAuthService>().GetUserData();
_wallsViewPresenter = ViewPresenterHelper.CreateViewPresenter<WallViewPresenter, IWallView, WallActivity>(this);
_listView = FindViewById<ListView>(Resource.Id.postList);
progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar1);
profile = await _wallsViewPresenter.GetProfile(int.Parse(_userData.Profile));
WallModel wall = SerializationService.DeSerialize<WallModel>(profile.Wall);
_posts = (List<PostModel>) (wall.Posts.ToList());
progressBar.Visibility = ViewStates.Gone;
_postListAdapter = new PostListAdapter(this, _posts);
_listView.Adapter = _postListAdapter;
SetListViewHeader();
_listView.AddHeaderView(_header);
FindViewById<TextView>(Resource.Id.details).Text = profile.Name;
}

Repeat async task by timer in xamarin forms

i'm new to xamarin.forms development and i'm still having my first steps from the few tutorials that are found on the net.
I have an async task that returns the time from date.jsontest.com
and i have a timer that decrements a text in a label.
i want to put the async task in the timer so that it repeats itself and displays the time on the label however im getting cannot convert async lamba to func
here's my code please help me, thanks
static async Task<string> RequestTimeAsync()
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync("http://date.jsontest.com/");
var jsonObject = JObject.Parse(jsonString);
return jsonObject["time"].Value<string>();
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
timeLabel.Text = await RequestTimeAsync();
Device.StartTimer(TimeSpan.FromSeconds(1), () => { // i want the taks to be put
//here so that it gets repeated
var number = float.Parse(button.Text) - 1;
button.Text = number.ToString();
return number > 0;
});
}
Reloading the Content Page in the timer would do the trick, so if anybody can please help me it would be appreciated
You just need to wrap the async method call in a Task.Run(), for example:
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Task.Run(async () =>
{
var time = await RequestTimeAsync();
// do something with time...
});
return true;
});
Solved
as simple as that
private async void ContinuousWebRequest()
{
while (_keepPolling)
{
timeLabel.Text = "call: "+counter+" "+ await RequestTimeAsync()+Environment.NewLine;
// Update the UI (because of async/await magic, this is still in the UI thread!)
if (_keepPolling)
{
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
}
static async Task<string> RequestTimeAsync()
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync("http://date.jsontest.com/");
var jsonObject = JObject.Parse(jsonString);
TimePage.counter++;
return jsonObject["time"].Value<string>();
}
}
A simple clock demonstrates a one second timer action:
Device.StartTimer(TimeSpan.FromSeconds(1), doitt);
bool doitt()
{
label1.Text = DateTime.Now.ToString("h:mm:ss");
return true;
}
or you can try this:
static async Task<string> RequestTimeAsync()
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync("http://date.jsontest.com/");
var jsonObject = JObject.Parse(jsonString);
return jsonObject["time"].Value<string>();
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
var Text = await RequestTimeAsync();
Device.StartTimer(TimeSpan.FromSeconds(1), () => {
// i want the taks to be put
//here so that it gets repeated
var jsonDateTsk = RequestTimeAsync();
jsonDateTsk.Wait();
var jsonTime = jsonDateTsk.Result;
var number = float.Parse(Text) - 1;
var btnText = $"{number}";
return number > 0;
});
}

Unresponsive UI while using Async & Await

I am trying to use async and await in my coding while I transfer big amounts of data from my WCF service to my WPF client application. Now it does take about 2-3 minutes to load the data, but now I struggle with my UI that's unresponsive for that total time. It's not very user-friendly. Is there a way to get my my UI responsive while the data loads with the coding that I currently have?
public pgSysproStock()
{
InitializeComponent();
SysproStock.WindowState = WindowState.Normal;
this.UpdateStockAsync();
}
private async void UpdateStockAsync()
{
dgSysproStock.IsEnabled = false;
using (TruckServiceClient TSC = new TruckServiceClient())
{
var allStock = await TSC.GetSysproStockAsync();
dgSysproStock.ItemsSource = allStock.Select(item =>
new AllStock
{
Id = item.Id,
StockCode = item.StockCode,
Description = item.Description,
ConvFactAltUom = item.ConvFactAltUom,
ConvMulDiv = item.ConvMulDiv,
ConvFactOthUom = item.ConvFactOthUom,
MulDiv = item.MulDiv,
Mass = item.Mass,
Updated_Supplier = item.Updated_Supplier,
CycleCount = item.CycleCount,
ProductClass = item.ProductClass.ToString(),
UnitCost = item.UnitCost,
Discount = item.Discount,
Warehouse = item.Warehouse,
MinimumStock = item.MinimumStock,
MaximumStock = item.MaximumStock,
StockForNow = item.StockForNow,
CoilWidth = item.CoilWidth,
SheetCoilLength = item.SheetCoilLength,
MaterialThickness = item.MaterialThickness
}).ToArray();
dgSysproStock.IsEnabled = true;
}
}
Thank you for any advice! :D
It seems that your operation is initiating operation incorrectly.
I suggest you create handler On Load or some other event for your window, mark it async and do the call from there. Should be simple as this:
private async void Form1_Load(object sender, EventArgs e)
{
dgSysproStock.IsEnabled = false;
using (TruckServiceClient TSC = new TruckServiceClient())
{
var allStock = await TSC.GetSysproStockAsync();
dgSysproStock.ItemsSource = allStock.Select(item =>
new AllStock
{
...
}).ToArray();
dgSysproStock.IsEnabled = true;
}
}
Note that I'm using Form.Load event but you may use different.

Nested Asynchronous function in Silverlight

I am trying to call a nested Asynchronous function but I am not getting the required data.
Since I am using a wcf service with Silverlight I can only use Asynchronous functions.
In my code I am saving a set of rows containing userdata. Before I save it I need to check the username is unique. Now I only need to find out the first one and then break out of loop and show a message to the user.for simplicity sake, I have stripped the function of all the extra data and this is how it looks
private void SaveUsers(bool CloseForm)
{
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
foreach (UserViewModel _User in _AllUsers)
{
//bool success = _dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
if (_User.Dirty && !_User.IsBlank)
{
_dataService.CheckIsUserNameUnique += (s, e) =>
{
if (e.IsUnique)
_UpdatedUsers.Add(_User.SaveAsUser());
else
{
_UpdatedUsers = new ObservableCollection<User>();
csaMessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
return;
}
};
_dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
}
_dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
if (e.Success)
{
}
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
}
BusyIndicator = true;
BusyMessage = "Saving...";
_dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, _UpdatedUsers, _DeletedProjectUsers);
}
In this case I am trying to find if the username is unique,show user a message and return.
Obviously it's not as simple as that.I have tried a couple more different ways but it didn't work. How do I get this working?
I think you can make your life easier by adding a couple of helper functions. The first one is an asynchronous function that checks whether a user is unique. You may need to add some code to set tcs.SetException if there is an error.
private Task<bool> IsUserUniqueAsync(UserViewModel user, DatabaseServiceLocal dataService)
{
var tcs = new TaskCompletionSource<bool>();
dataService.CheckIsUserNameUnique += (s, e) =>
{
tcs.SetResult(e.IsUnique);
};
dataService.IsUserNameUnique(user.UserName, user.UserID, Database.CurrentClient.ClientID);
return tcs.Task;
}
The second one updates all the users asynchrnously
public Task<bool> UpdateUsersAsync(ObservableCollection<User> updatedUsers, DatabaseServiceLocal dataService)
{
var tcs = new TaskCompletionSource<bool>();
BusyIndicator = true;
BusyMessage = "Saving...";
dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
tcs.SetResult(e.Success);
};
dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, updatedUsers, _DeletedProjectUsers);
return tcs.Task;
}
Then your SaveUsers method becomes a bit simpler.
private async void SaveUsers(bool CloseForm)
{
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
Dictionary<Task<bool>, User> tasks = new Dictionary<Task<bool>, User>();
// start all tasks in parallel
foreach (UserViewModel _User in _AllUsers)
{
if (_User.Dirty && !_User.IsBlank)
{
tasks.Add(IsUserUniqueAsync(_User, _dataService), _User);
}
}
// process each task as it completes
while(tasks.Count() > 0 )
{
var task = await Task.WhenAny(tasks.Keys.ToArray());
if(task.Result)
{
_UpdatedUsers.Add(_User.SaveAsUser());
}
else
{
MessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
return;
}
tasks.Remove(task);
}
if( await UpdateUsersAsync(_UpdatedUsers, _dataService))
{
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
}
}
Your code would more or less look like this.
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
int _verifiedUsersCount = 0;
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
//Verify unique users
private void SaveUsers(bool CloseForm)
{
_dataService.CheckIsUserNameUnique += CheckIsUserNameUnique;
foreach (UserViewModel _User in _AllUsers)
{
//bool success = _dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
if (_User.Dirty && !_User.IsBlank)
{
_dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
}
}
}
//Store verified users to save
private void CheckIsUserNameUnique(object s, CheckIsUserNameUniqueEventArgs e)
{
if (e.IsUnique)
_UpdatedUsers.Add(_User.SaveAsUser());
else
{
csaMessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
}
verifiedUsersCount++;
//Call after all the users have been verified for uniqueness
if (_AllUsers.Count() == verifiedUsersCount)
{
OnUniqueUserVerifyComplete();
}
}
//Save verified users
private void OnUniqueUserVerifyComplete()
{
//No unique users
if (_UpdatedUsers.Count < 1) { return; }
_dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
if (e.Success)
{
}
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
};
BusyIndicator = true;
BusyMessage = "Saving...";
_dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, _UpdatedUsers, _DeletedProjectUsers);
}

Categories