I am new the using Task.Run() along with async and await to make UI more responsive, so likely I have not implemented something correctly.
I have reviewed the great articles from Stephen Cleary about using AsyncCommands and have used his code from Patterns for Asynchronous MVVM Applications: Commands as a basis for having a responsive UI but when I run the code it still seems to freeze up (I am not able to move the window or interact with other buttons until the function has fully finished.
I am trying to perform a search which usually takes 5-10 seconds to return. Below is the code that creates the AsyncCommand along with the what the function does.
Code:
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(() => Search());
}
return _SearchCommand;
}
}
private async Task Search()
{
IEnumerable<PIPoint> points = await SearchAsync(_CurrentPIServer, NameSearch, PointSourceSearch).ConfigureAwait(false);
SearchResults.Clear();
foreach (PIPoint point in points)
{
SearchResults.Add(point.Name);
}
}
private async Task<IEnumerable<PIPoint>> SearchAsync(string Server, string NameSearch, string PointSourceSearch)
{
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
server.Connect();
return await Task.Run<IEnumerable<PIPoint>>(()=>PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch)).ConfigureAwait(false);
}
}
I am thinking that the issue is somewhere in how I am pushing the long running function onto a thread and its not getting off of the UI thread or my understanding of how Tasks and async/await are completely off.
EDIT 1:
Following Stephen's answer I updated the functions, but I am not seeing any change in the UI responsiveness. I created a second command that performs the same actions and I get the same response from UI in either case. The code now looks like the following
CODE:
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(async () =>
{
var results = await Task.Run(()=>Search(_CurrentPIServer, NameSearch, PointSourceSearch));
SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
});
}
return _SearchCommand;
}
}
public ICommand SearchCommand2
{
get
{
if (_SearchCommand2 == null)
{
_SearchCommand2 = new RelayCommand(() =>
{
var results = Search(_CurrentPIServer, NameSearch, PointSourceSearch);
SearchResults = new ObservableCollection<string>(results.Select(x => x.Name));
}
,()=> true);
}
return _SearchCommand2;
}
}
private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
server.Connect();
return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
}
I must be missing something but I am not sure what at this point.
EDIT 2:
After more investigation on what was taking so long it turns out the iterating of the list after the results are found is what was hanging the process. By simply changing what the Search function was returning and having it already iterated over the list of objects allows for the UI to remain responsive. I marked Stephen's answer as correct as it handled my main problem of properly moving work off of the UI thread I just didnt move the actual time consuming work off.
My first guess is that the work queued to Task.Run is quite fast, and the delay is caused by other code (e.g., PIServer.Connect).
Another thing of note is that you are using ConfigureAwait(false) in Search which updates SearchResults - which I suspect is wrong. If SearchResults is bound to the UI, then you should be in the UI context when updating it, so ConfigureAwait(false) should not be used.
That said, there's a Task.Run principle that's good to keep in mind: push Task.Run as far up your call stack as possible. I explain this in more detail on my blog. The general idea is that Task.Run should be used to invoke synchronous methods; it shouldn't be used in the implementation of an asynchronous method (at least, not one that is intended to be reused).
As a final note, async is functional in nature. So it's more natural to return results than update collections as a side effect.
Combining these recommendations, the resulting code would look like:
private IEnumerable<PIPoint> Search(string Server, string NameSearch, string PointSourceSearch)
{
PIServers KnownServers = new PIServers();
PIServer server = KnownServers[Server];
// TODO: If "Connect" or "FindPIPoints" are naturally asynchronous,
// then this method should be converted back to an asynchronous method.
server.Connect();
return PIPoint.FindPIPoints(server, NameSearch, PointSourceSearch);
}
public ICommand SearchCommand
{
get
{
if (_SearchCommand == null)
{
_SearchCommand = AsyncCommand.Create(async () =>
{
var results = await Task.Run(() =>
Search(_CurrentPIServer, NameSearch, PointSourceSearch));
SearchResults = new ObservableCollection<string>(
results.Select(x => x.Name));
});
}
return _SearchCommand;
}
}
Related
Going through:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
and
How to bind WPF button to a command in ViewModelBase?
I've come up with the following code of reading and rendering file contents to a window asynchronously (or synchronously?):
class AppliedJobsViewModel
{
private TexParser texParser;
private ICommand _openTexClick;
public ICommand OpenTexClick
{
get
{
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
}
}
public bool CanExecute
{
get
{
// check if executing is allowed, i.e., validate, check if a process is running, etc.
return true;
}
}
public async Task ReadAndParseTexFile()
{
if (texParser == null)
{
texParser = new TexParser();
}
// Read file asynchronously here
await ReadAndParseTexFileAsync();
string[][] appliedJobs = texParser.getCleanTable();
}
private async Task ReadAndParseTexFileAsync()
{
texParser.ReadTexFile();
await Task.Delay(100);
}
public ObservableCollection<AppliedJob> AppliedJobs {
get;
set;
}
}
which "works" but I don't like that Task.Delay(100) (constant wait time).
VS also tells me at line:
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
"Because this call is not awaited, the execution of this method continues before the call is completed".
But this method is what binds the code to the wpf view. Isn't it async by default?
Just dropping an async in a method or task signature doesn't mean you're doing anything asynchronously.
Read this:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/using-async-for-file-access
Note that it is not just a case of adding async to the name than makes File.ReadAllTextAsync asynchronous.
You should read up on async and await.
eg
https://blog.stephencleary.com/2012/02/async-and-await.html
This is a complex subject. It will take careful reading and consideration to understand.
Once you've read that, reconsider your code.
In that warning your ide was telling you your code is synchronous.
You have synchronous code in there reading that file.
Sticking an await some-other-code in there does not make your file reading code asynchronous, it just means the ide sees an await Task. A pointless await task.
Now you know it's pointless.
Take out
await Task.Delay(100);
Replacing your file reading code with
string contents = await File.ReadAllTextAsync(filePath);
Could make the file reading asynchronous.
If this is a large file being read into memory then just reading it might have significant overhead and your UI may well remain responsive.
If you were doing some substantial processing of a file then you would probably do better pushing that onto a background thread.
You can do
await Task.Run(() => ProcessFileText(someurl));
Here ProcessFileText would be a Task or maybe Task<string[]> and return a string or string array. It would run on a background thread and return once completed.
Seeing as this is a different thread then it won't freeze your UI if it takes a while to do it's work.
Note that since it is now not on the UI thread this task cannot read or write UI properties or you'll get threading errors.
Code in the line(s) after that await is back on the ui thread.
What I am trying to do here is loop a listview full of URLs, check if the source code contains a string, if it does then update the UI listview with YES or NO.
I had forgotten about the Parallel.ForEach method, so decided to try it out (I'm not even sure if it's the best solution for this)
Parallel.ForEach(listViewMain.Items.Cast<ListViewItem>(), row =>
{
try
{
string html = Helpers.GetRequest(row.Text);
if (html.Contains(txtBoxFind.Text))
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
} catch(Exception) {
}
});
The process is fairly simple doing it without the Parallel.ForEach but the UI is still locking up, have i implemented it right? Helpers.GetRequest simply returns the raw HTML to be checked, i thought using the Parallel.ForEach would stop the UI locking while processing or have i got it wrong, any help is appreciated.
Before we begin, do note that Parallel.ForEach in itself is a blocking call, so that is why you experience the UI being not responsive.
A good read is Avoid Executing Parallel Loops on the UI Thread from the MS docs:
... the parallel loop blocks the UI thread on which it’s executing until all iterations are complete.
That said, you should use a Task based approach if not dealing with CPU bound work but with I/O bound work. Since you are dealing with network calls you should stick to tasks. Try this:
public async Task DoSomething()
{
// Process the items parallel
await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
{
// wrap the long running call in a async Task
string html = await Task.Run(() => Helpers.GetRequest(row.Text));
// no need for context capturing and invokes, this is running on the UI thread
var containsText = html.Contains(txtBoxFind.Text);
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
}));
}
It would be even better when you can make Helpers.GetRequest(row.Text) a Task based method, then you could do:
public async Task DoSomething()
{
// Process the items parallel
await Task.WhenAll(listViewMain.Items.Cast<ListViewItem>().Select(async row =>
{
// wrap the long running call in a async Task
string html = await Helpers.GetRequestAsync(row.Text);
// no need for context capturing and invokes, this is running on the UI thread
var containsText = html.Contains(txtBoxFind.Text);
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
}));
}
but we need to see the code of Helpers.GetRequest(row.Text) to assist you with that.
EDIT
You've shown the code of GetRequest. WebClient is not task based, try HttpClient:
public async Task<string> GetRequestAsync(string url)
{
var html = "";
using (HttpClient wc = new HttpClient())
{
html = await wc.GetStringAsync(url);
}
return html;
}
the Parallel.ForEach is executing on the UI thread (current thread) and it will not provide more performance for you in case of non-blocking UI. If you want to avoid the UI block, you could try using the async methods, for sample:
Task.Run(() => CheckItems());
Given you can implement an async version of the GetRequest method, you could implement an async method to do this, for sample:
public async Task CheckItems()
{
foreach (var row in listViewMain.Items.Cast<ListViewItem>())
{
try
{
string html = await Helpers.GetRequestAsync(row.Text);
if (html.Contains(txtBoxFind.Text))
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
} catch(Exception ex) {
}
}
}
Don't use the Parallel.ForEach() for this. Parallel.ForEach() is used for CPU bound work. Not IO.
I would do something like: (I didn't tested it, might contains some typo's) (used notepad)
So you might use this for the idea:
public async void Button_Click(object sender, EventArg e)
{
await CheckItems(listViewMain.Items.Cast<ListViewItem>());
}
public async Task CheckItems(IEnumerable<ListViewItem> items)
{
// Capture the UI thread synchronization.
var context = SynchronizationContext.Current;
var tasks = new List<Task>();
// create tasks.
foreach (var row in items)
{
tasks.Add(Task.Run(() =>
{
// the lookup on a (probably) threadpool thread
string html = Helpers.GetRequest(row.Text);
// the processing here..
var containsText = html.Contains(txtBoxFind.Text);
// post the result (and touching gui items in the UI thread)
// this.Invoke() is also and might be the best solution.
context.Post(() =>
{
if (containsText)
{
row.SubItems[3].Text = "YES";
}
else
{
row.SubItems[3].Text = "NO";
}
});
}));
}
// wait for them
await Task.WhenAll(tasks);
}
While I was adding some comment, you could also use the this.Invoke() for this (instead of the SynchronizationContext)
gtg.
I have a list of objects that I need to run a long running process on and I would like to kick them off asynchronously, then when they are all finished return them as a list to the calling method. I've been trying different methods that I have found, however it appears that the processes are still running synchronously in the order that they are in the list. So I am sure that I am missing something in the process of how to execute a list of tasks.
Here is my code:
public async Task<List<ShipmentOverview>> GetShipmentByStatus(ShipmentFilterModel filter)
{
if (string.IsNullOrEmpty(filter.Status))
{
throw new InvalidShipmentStatusException(filter.Status);
}
var lookups = GetLookups(false, Brownells.ConsolidatedShipping.Constants.ShipmentStatusType);
var lookup = lookups.SingleOrDefault(sd => sd.Name.ToLower() == filter.Status.ToLower());
if (lookup != null)
{
filter.StatusId = lookup.Id;
var shipments = Shipments.GetShipments(filter);
var tasks = shipments.Select(async model => await GetOverview(model)).ToList();
ShipmentOverview[] finishedTask = await Task.WhenAll(tasks);
return finishedTask.ToList();
}
else
{
throw new InvalidShipmentStatusException(filter.Status);
}
}
private async Task<ShipmentOverview> GetOverview(ShipmentModel model)
{
String version;
var user = AuthContext.GetUserSecurityModel(Identity.Token, out version) as UserSecurityModel;
var profile = AuthContext.GetProfileSecurityModel(user.Profiles.First());
var overview = new ShipmentOverview
{
Id = model.Id,
CanView = true,
CanClose = profile.HasFeatureAction("Shipments", "Close", "POST"),
CanClear = profile.HasFeatureAction("Shipments", "Clear", "POST"),
CanEdit = profile.HasFeatureAction("Shipments", "Get", "PUT"),
ShipmentNumber = model.ShipmentNumber.ToString(),
ShipmentName = model.Name,
};
var parcels = Shipments.GetParcelsInShipment(model.Id);
overview.NumberParcels = parcels.Count;
var orders = parcels.Select(s => WareHouseClient.GetOrderNumberFromParcelId(s.ParcelNumber)).ToList();
overview.NumberOrders = orders.Distinct().Count();
//check validations
var vals = Shipments.GetShipmentValidations(model.Id);
if (model.ValidationTypeId == Constants.OrderValidationType)
{
if (vals.Count > 0)
{
overview.NumberOrdersTotal = vals.Count();
overview.NumberParcelsTotal = vals.Sum(s => WareHouseClient.GetParcelsPerOrder(s.ValidateReference));
}
}
return overview;
}
It looks like you're using asynchronous methods while you really want threads.
Asynchronous methods yield control back to the calling method when an async method is called, then wait until the methods has completed on the await. You can see how it works here.
Basically, the only usefulness of async/await methods is not to lock the UI, so that it stays responsive.
If you want to fire multiple processings in parallel, you will want to use threads, like such:
using System.Threading.Tasks;
public void MainMethod() {
// Parallel.ForEach will automagically run the "right" number of threads in parallel
Parallel.ForEach(shipments, shipment => ProcessShipment(shipment));
// do something when all shipments have been processed
}
public void ProcessShipment(Shipment shipment) { ... }
Marking the method as async doesn't auto-magically make it execute in parallel. Since you're not using await at all, it will in fact execute completely synchronously as if it wasn't async. You might have read somewhere that async makes functions execute asynchronously, but this simply isn't true - forget it. The only thing it does is build a state machine to handle task continuations for you when you use await and actually build all the code to manage those tasks and their error handling.
If your code is mostly I/O bound, use the asynchronous APIs with await to make sure the methods actually execute in parallel. If they are CPU bound, a Task.Run (or Parallel.ForEach) will work best.
Also, there's no point in doing .Select(async model => await GetOverview(model). It's almost equivalent to .Select(model => GetOverview(model). In any case, since the method actually doesn't return an asynchronous task, it will be executed while doing the Select, long before you get to the Task.WhenAll.
Given this, even the GetShipmentByStatus's async is pretty much useless - you only use await to await the Task.WhenAll, but since all the tasks are already completed by that point, it will simply complete synchronously.
If your tasks are CPU bound and not I/O bound, then here is the pattern I believe you're looking for:
static void Main(string[] args) {
Task firstStepTask = Task.Run(() => firstStep());
Task secondStepTask = Task.Run(() => secondStep());
//...
Task finalStepTask = Task.Factory.ContinueWhenAll(
new Task[] { step1Task, step2Task }, //more if more than two steps...
(previousTasks) => finalStep());
finalStepTask.Wait();
}
I am trying to make a method of mine into something that can be called asynchronously.
Normally from the AddQueue Method, I would just call the ListOfJobsInQueue methods in the WorkingSession class, get its result and be done with it.
Using what info I could find regarding Async programming on here, I have done up the below code, but it seems to be getting stuck on the CurrentPageCode property call.
It does not even get to the MessageBox.Show("Processing complete with " + queueResult.Count + " rows"); line.
Could someone please assist and show me where I'm going wrong?
//Primary Class
public void AddQueue()
{
MessageBox.Show(GetJobsFromQueueAsync().Result.Count().ToString());
}
async Task<List<string>> GetJobsFromQueueAsync()
{
Task<List<string>> getJobsTask = WorkingSession.GetlistOfJobsAsync();
List<string> queueResult = await getJobsTask;
MessageBox.Show("Processing complete with " + queueResult.Count + " rows");
return queueResult;
}
//***
//WorkingSession Class
public Task<List<string>> GetlistOfJobsAsync()
{
return Task.Run<List<string>>(() =>
{
return ListOfJobsInQueue();
});
}
public List<string> ListOfJobsInQueue()
{
if (CurrentPageCode == "CS1")
{
List<string> actionList = new List<string>();
short pageNum = PageCurrent;
short pageMax = PageMax;
for (short atPage = pageNum; atPage <= pageMax; atPage++)
{
//Scan each job on the current queue page
for (int lineNum = 5; lineNum < 18; lineNum++)
{
string reference = GetJobText(new Coordinate { row = lineNum });
actionList.Add(reference);
}
//Once finished with this job page, goto the next
SendCMDKey(Mnemonic.F8);
}
return actionList;
}
else
{
return null;
}
}
//Other method / property signatures (for reference)
public string CurrentPageCode;
public bool SendCMDKey(Mnemonic command)
public string GetJobText(Coordinate coordinate)
//***
The deadlock problem is actually this method:
public void AddQueue()
{
MessageBox.Show(GetJobsFromQueueAsync().Result.Count().ToString());
}
Calling Task.Wait or Task<T>.Result should be avoided in async code. I explain the deadlock in full on my blog, but the summary version is that await will capture a context (in this case, the UI context) and attempt to resume its async method on that context (in this case, on the UI thread). With some contexts (e.g., the UI context), if you block a thread in that context (e.g., calling Task<T>.Result on the UI thread), then the async method cannot resume on that context, causing a deadlock.
To fix it, use async all the way:
public async Task AddQueueAsync()
{
var jobs = await GetJobsFromQueueAsync();
MessageBox.Show(jobs.Count().ToString());
}
This code is also not ideal, though in a much more subtle way:
public Task<List<string>> GetlistOfJobsAsync()
{
return Task.Run<List<string>>(() =>
{
return ListOfJobsInQueue();
});
}
By wrapping an entire method's logic in Task.Run, what you're really doing is writing a "fake asynchronous" method. It's got an asynchronous signature but the logic is just synchronous work on a background thread.
It's best to push any Task.Run use as far towards the UI layer as possible; keep it out of any reusable library methods. Make your APIs tell the truth: have synchronous signatures for synchronous work. I have a blog series that goes into detail.
The simplest that I can
public async Task<int> GetWorkFlowStageAsync(string tracker,CancellationToken? token = null)
{
return await Task.FromResult(0);
}
I'm trying to transition from the Event-based Asynchronous Pattern where I tracked running methods using unique id's and the asynoperationmanager. As this has now been dropped from Windows 8 Apps I'm trying to get a similar effect with Async/Await but can't quite figure out how.
What I'm trying to achieve is something like
private async Task updateSomething()
{
if(***the method is already running***)
{
runagain = true;
}
else
{
await someMethod();
if (runagain)
{
run the method again
}
}
}
The part I'm struggling with is finding out if the method is running. I've tried creating a Task and looking at the status of both that and the .status of the async method but they don't appear to be the correct place to look.
Thanks
UPDATE: This is the current code I use in .net 4 to achieve the same result. _updateMetaDataAsync is a class based on the Event-Based Asynchronous Pattern.
private void updateMetaData()
{
if (_updateMetaDataAsync.IsTaskRunning(_updateMetaDataGuid_CheckAllFiles))
{
_updateMetaDataGuid_CheckAllFiles_Again = true;
}
else
{
_updateMetaDataGuid_CheckAllFiles_Again = false;
_updateMetaDataAsync.UpdateMetaDataAsync(_updateMetaDataGuid_CheckAllFiles);
}
}
private void updateMetaDataCompleted(object sender, UpdateMetaDataCompletedEventArgs e)
{
if (_updateMetaDataGuid_CheckAllFiles_Again)
{
updateMetaData();
}
}
async/await itself is intended to be used to create sequential operations executed asynchronously from the UI thread. You can get it to do parallel operations, but generally the operations "join" back to the UI thread with some sort of result. (there's also the possibility of doing "fire-and-forget" types of asynchronous operations with await but it's not recommended). i.e. there's nothing inherent to async/await to support progress reporting.
You can get progress out of code using async/await; but you need to use new progress interfaces like IProgress<T>. For more info on progress reporting with async/await, see http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx. Migrating to this should just be a matter of calling an IProgress delegate instead of a Progress event.
If you're using a Task you've created, you can check the Task's Status property (or just see Task.IsCompleted if completion is the only state you are interested in).
That being said, await will not "return" until the operation either completes, raises an exception, or cancels. You can basically safely assume that, if you're still waiting on the "await", your task hasn't completed.
SemaphoreSlim queueToAccessQueue = new SemaphoreSlim(1);
object queueLock = new object();
long queuedRequests = 0;
Task _loadingTask;
public void RetrieveItems() {
lock (queueLock) {
queuedRequests++;
if (queuedRequests == 1) { // 1 is the minimum size of the queue before another instance is queued
_loadingTask = _loadingTask?.ContinueWith(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release()
}) ?? Task.Run(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release();
});
}
}
}
public void RunTheMethodAgain() {
** run the method again **
}
The added bonus is that you can see how many items are sitting in the queue!