I've been working on a project and saw the below code. I am new to the async/await world. As far as I know, only a single task is performing in the method then why it is decorated with async/await. What benefits I am getting by using async/await and what is the drawback if I remove async/await i.e make it synchronous I am a little bit confused so any help will be appreciated.
[Route("UpdatePersonalInformation")]
public async Task<DataTransferObject<bool>> UpdatePersonalInformation([FromBody] UserPersonalInformationRequestModel model)
{
DataTransferObject<bool> transfer = new DataTransferObject<bool>();
try
{
model.UserId = UserIdentity;
transfer = await _userService.UpdateUserPersonalInformation(model);
}
catch (Exception ex)
{
transfer.TransactionStatusCode = 500;
transfer.ErrorMessage = ex.Message;
}
return transfer;
}
Service code
public async Task<DataTransferObject<bool>> UpdateUserPersonalInformation(UserPersonalInformationRequestModel model)
{
DataTransferObject<bool> transfer = new DataTransferObject<bool>();
await Task.Run(() =>
{
try
{
var data = _userProfileRepository.FindBy(x => x.AspNetUserId == model.UserId)?.FirstOrDefault();
if (data != null)
{
var userProfile = mapper.Map<UserProfile>(model);
userProfile.UpdatedBy = model.UserId;
userProfile.UpdateOn = DateTime.UtcNow;
userProfile.CreatedBy = data.CreatedBy;
userProfile.CreatedOn = data.CreatedOn;
userProfile.Id = data.Id;
userProfile.TypeId = data.TypeId;
userProfile.AspNetUserId = data.AspNetUserId;
userProfile.ProfileStatus = data.ProfileStatus;
userProfile.MemberSince = DateTime.UtcNow;
if(userProfile.DOB==DateTime.MinValue)
{
userProfile.DOB = null;
}
_userProfileRepository.Update(userProfile);
transfer.Value = true;
}
else
{
transfer.Value = false;
transfer.Message = "Invalid User";
}
}
catch (Exception ex)
{
transfer.ErrorMessage = ex.Message;
}
});
return transfer;
}
What benefits I am getting by using async/await
Normally, on ASP.NET, the benefit of async is that your server is more scalable - i.e., can handle more requests than it otherwise could. The "Synchronous vs. Asynchronous Request Handling" section of this article goes into more detail, but the short explanation is that async/await frees up a thread so that it can handle other requests while the asynchronous work is being done.
However, in this specific case, that's not actually what's going on. Using async/await in ASP.NET is good and proper, but using Task.Run on ASP.NET is not. Because what happens with Task.Run is that another thread is used to run the delegate within UpdateUserPersonalInformation. So this isn't asynchronous; it's just synchronous code running on a background thread. UpdateUserPersonalInformation will take another thread pool thread to run its synchronous repository call and then yield the request thread by using await. So it's just doing a thread switch for no benefit at all.
A proper implementation would make the repository asynchronous first, and then UpdateUserPersonalInformation can be implemented without Task.Run at all:
public async Task<DataTransferObject<bool>> UpdateUserPersonalInformation(UserPersonalInformationRequestModel model)
{
DataTransferObject<bool> transfer = new DataTransferObject<bool>();
try
{
var data = _userProfileRepository.FindBy(x => x.AspNetUserId == model.UserId)?.FirstOrDefault();
if (data != null)
{
...
await _userProfileRepository.UpdateAsync(userProfile);
transfer.Value = true;
}
else
{
transfer.Value = false;
transfer.Message = "Invalid User";
}
}
catch (Exception ex)
{
transfer.ErrorMessage = ex.Message;
}
return transfer;
}
The await keyword only indicates that the execution of the current function is halted until the Task which is being awaited is completed. This means if you remove the async, the method will continue execution and therefore immediately return the transfer object, even if the UpdateUserPersonalInformation Task is not finished.
Take a look at this example:
private void showInfo()
{
Task.Delay(1000);
MessageBox.Show("Info");
}
private async void showInfoAsync()
{
await Task.Delay(1000);
MessageBox.Show("Info");
}
In the first method, the MessageBox is immediately displayed, since the newly created Task (which only waits a specified amount of time) is not awaited. However, the second method specifies the await keyword, therefore the MessageBox is displayed only after the Task is finished (in the example, after 1000ms elapsed).
But, in both cases the delay Task is ran asynchronously in the background, so the main thread (for example the UI) will not freeze.
The usage of async-await mechanism mainly used
when you have some long calculation process which takes some time and you want it to be on the background
in UI when you don't want to make the main thread stuck which will be reflected on UI performance.
you can read more here:
https://learn.microsoft.com/en-us/dotnet/csharp/async
Time Outs
The main usages of async and await operates preventing TimeOuts by waiting for long operations to complete. However, there is another less known, but very powerful one.
If you don't await long operation, you will get a result back, such as a null, even though the actual request as not completed yet.
Cancellation Tokens
Async requests have a default parameter you can add:
public async Task<DataTransferObject<bool>> UpdatePersonalInformation(
[FromBody] UserPersonalInformationRequestModel model,
CancellationToken cancellationToken){..}
A CancellationToken allows the request to stop when the user changes pages or interrupts the connection. A good example of this is a user has a search box, and every time a letter is typed you filter and search results from your API. Now imagine the user types a very long string with say 15 characters. That means that 15 requests are sent and 15 requests need to be completed. Even if the front end is not awaiting the first 14 results, the API is still doing all the 15 requests.
A cancellation token simply tells the API to drop the unused threads.
I would like to chime in on this because most answers although good, do not point to a definite time when to use and when not.
From my experience, if you are developing anything with a front-end, add async/await to your methods when expecting output from other threads to be input to your UI. This is the best strategy for handling multithread output and Microsoft should be commended to come out with this when they did. Without async/await you would have to add more code to handle thread output to UI (e.g Event, Event Handler, Delegate, Event Subscription, Marshaller).
Don't need it anywhere else except if using strategically for slow peripherals.
Related
I have this method that increases the views of a video when it's watched.
If the video of the person who published it reaches 200 views, I send an congrats email to him,
but if the email sending DELAYS, I don't want the user to wait for that delay to watch the video.
I need to make Video loading not depend on email sending.
I have used Task.Run and Task.Factory.StartNew and inside it Parallell
I don't have experience with tasks/threads so which is the best way to implement it.
public async Task<ServiceResponse<ViewedVideoDto>> AddVideoView(int candidateVideoId)
{
var video = await _dbcontext.CandidateFiles.Include(x=>x.Candidate).ThenInclude(x=>x.User).FirstOrDefaultAsync(x => x.Id == candidateVideoId);
if (video is null)
{
return new ServiceResponse<ViewedVideoDto>().NotFound(nameof(Resource.VideoEmpty), Resource.VideoEmpty);
}
video.VideoViews += 1;
_dbcontext.Update(video);
await _dbcontext.SaveChangesAsync();
if (video.VideoViews == 200)
{
//Method 1
await Task.Run(() =>
_candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
video.Candidate.User.FullName));
//Method 2
Task.Factory.StartNew(() => Parallel.Invoke(() =>
{
_candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
video.Candidate.User.FullName);
}));
}
return new ServiceResponse<ViewedVideoDto>()
{
Data = new ViewedVideoDto()
{
VideoId = video.Id
}
};
}
public async Task Achieved200VideoViews(string receiverEmail, string fullName)
{
var candidateTemplate = EmailTemplate.Return(EmailTemplateConst.CanEmailTempFolder, EmailTemplateConst.CanAchieved200VideoViews,fullName);
var innoEmail = new InnoEmail()
{
Email = receiverEmail,
Subject = "Touchpoint after 200 views",
HtmlMessage = candidateTemplate
};
await _emailService.SendAsync(innoEmail);
public async Task<bool> SendAsync(Email email)
{
try
{
var apiKey = _configuration.GetSection("SENDGRID_API_KEY").GetSection("SecretKey").Value;
var Email = _configuration.GetSection("SENDGRID_API_KEY").GetSection("Email").Value;
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress(Email, "test"),
Subject = email.Subject,
PlainTextContent = null,
HtmlContent = email.HtmlMessage
};
msg.AddTo(new EmailAddress(email.Email));
var response = await client.SendEmailAsync(msg).ConfigureAwait(false);
response.StatusCode.ToString();
return true;
}
catch (System.Exception e)
{
return false;
}
}
The Method 1 (await Task.Run(() =>) will not achieve the declared goal cause you are awaiting the result which basically will suspend the AddVideoView execution until the Achieved200VideoViews finishes. If you want fire-and-forget behaviour - remove the await.
Based on Achieved200VideoViews implementation, basically it just sends mail - I would say that it does not matter which one is better cause both fire-and-forget approaches are not good enough, cause they lack observability (for example email sending can fail for multiple reasons or app will be restarted during the process).
I would suggest and approach similar to transactional outbox pattern - add either a flag to video table or separate table which will store info about notification about 200 views send (something like a video.ToSend200ViewNotification) and update that in AddVideoView and then process that table in some background job handler for example using hosted services or Hangouts or Quartz (or add flag Is200ViewNotificationSend and process all videos with views >= 200 and Is200ViewNotificationSend set to false).
If you are ok with limitations of fire-and-forget approach for synchronous operations Task.Run should be preferable (without await). Task.Factory.StartNew, as written in the docs TaskFactory.StartNew is more suitable for more complicated scenarios (also it is not task-aware and in "ordinary" cases will not handle methods returning Task's correctly):
Starting with the .NET Framework 4.5, the Task.Run method is the recommended way to launch a compute-bound task. Use the StartNew method only when you require fine-grained control for a long-running, compute-bound task. This includes scenarios in which you want to control the following:
Task creation options. Tasks created by the Task.Run method by default are created with the TaskCreationOptions.DenyChildAttach option. To override this behavior, or to provide other TaskCreationOptions options, call a StartNew overload.
Parameter passing. The overloads of the Task.Run method do not allow you to pass a parameter to the task delegate. Overloads of the StartNew method do.
The task scheduler. The overloads of the Task.Run method use the default task scheduler. To control the task scheduler, call a StartNew overload with a scheduler parameter. For more information, see TaskScheduler.
But since your Achieved200VideoViews method already returns Task and assuming it is "truly async" then just skipping await on the invokation should be fine to achieve fire-and-forget functionality (and I would say it is preferable cause it should be less resource-consuming):
_ = _candidateEmailService.Achieved200VideoViews(video.Candidate.User.Email,
video.Candidate.User.FullName);
I have this code to check if Server is available or not:
public static async Task<bool> PingServer()
{
System.Net.NetworkInformation.Ping p1 = new System.Net.NetworkInformation.Ping();
System.Net.NetworkInformation.PingReply PR = await p1.SendPingAsync("pc2");
// check when the ping is not success
if (PR.Status != System.Net.NetworkInformation.IPStatus.Success)
{
return false;
}
return true;
}
Now, my problem is that SendPingAsync does not support CancellationTokenSource so how can I cancel the last ping to perform a new one to prevent a lot of pings?
When the server is lost it takes seconds to PingServer() return a false value.
This is how I call PingServer():
var task = await PingServer();
if (task == true)
{
//do some stuff like retrieve a table data from SQL server.
}
Why I use ping? because when I want to get a table from SQL server if the server is disconnected my app enter a breakpoint.
public async static Task<System.Data.Linq.Table<Equipment>> GetEquipmentTable()
{
try
{
DataClassesDataContext dc = new DataClassesDataContext();
// note : i tried so many ways to get table synchronize but it still freeze my UI !
return await Task.Run(() => dc.GetTable<Equipment>());
}
catch
{
return null;
}
}
EDIT : I used ping to reduce chance of entering my app in brake mode by getting a table from database. is there any other way to prevent break mode ? is it best way to ping server before calling dc.GetTable() ?
Have you considered using the timeout parameter?
From the documentation:
This overload allows you to specify a time-out value for the
operation.
If that doesn't suffice and your problem is that the ping call is blocking, you could perform it on a background thread providing this is tightly controlled.
Consider
public static async Task<bool> PingServer() {
using (var ping = new System.Net.NetworkInformation.Ping()) {
try {
var maxDelay = TimeSpan.FromSeconds(2); //Adjust as needed
var tokenSource = new CancellationTokenSource(maxDelay);
System.Net.NetworkInformation.PingReply PR = await Task.Run(() =>
ping.SendPingAsync("pc2"), tokenSource.Token);
// check when the ping is not success
if (PR.Status != System.Net.NetworkInformation.IPStatus.Success) {
return false;
}
return true;
} catch {
return false;
}
}
}
Where the ping is done with a cancellation token using Task.Run; If the ping result returns before the allotted time then all is well.
wrapped the component in a using to dispose of it when exiting function.
The system you depend on might fail, or the connection can go down right after your ping, when you're executing your code.
This is why a retry strategy is a much more robust approach then simply pinging a system before calling it.
Here is how you can implement a retry Cleanest way to write retry logic?
I would go with a retry approach, but if you still want to stay with your design you could
Schedule a periodic task to ping the system in question Is there a Task based replacement for System.Threading.Timer?
Make sure you schedule this periodic task in one central place (application startup or the like)
Invoke your PingServer from this periodic task and make sure you call Ping.SendAsync with PingOptions.Timeout being set, see this overload
From your PingServer set some kind of shared state, it could be a static variable, or an implementation of the Registry pattern
Make sure your shared state is thread-safe
The rest of your code can call this shared state, to find out if a system is online and available
As you can see, this approach is more complex, but you will prevent "lots of pings" to the system you depend on
I started to look into Task, async/await concepts is c# and I'm having big problems understanding it, well at least i don't know how to implement it. I started rewriting an older test program i had written before, but now instead of threading i want to use these new concepts. Basically the layout is as it follows:
I have a simple class where i download the HTML content of a web page.
I process that in another class where i basically just parse the page to my model. Later on i want to display that to my UI.
The problem is that my program is not responsive, it blocks the UI while I'm processing the info.
I started learning this 2 days ago, i have read a lot of stuff online, including MSDN and some blogs but yet I'm unable to figure it out. Maybe someone can provide a look as well
HtmlDOwnloadCOde:
public async Task<string> GetMangaDescriptionPage(string detailUrl)
{
WebClient client = new WebClient();
Stream data = await client.OpenReadTaskAsync(detailUrl);
StreamReader reader = new StreamReader(data);
string s = reader.ReadToEnd();
data.Dispose();
reader.Dispose();
data.Close();
reader.Close();
return s;
}
My parse class code:
public async Task<MangaDetailsModel> ParseMangaDescriptionPage()
{
ParseOneManga pom = new ParseOneManga();
string t1 = await pom.GetMangaDescriptionPage(selectedManga.url);
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(t1);
var divs = htmlDoc.DocumentNode.Descendants("div").Where(x => x.Attributes.Contains("id") &&
x.Attributes["id"].Value.Contains("title")).ToArray();
mangaDetails.mangaName = divs[0].Element("h1").InnerText;
mangaDetails.description = divs[0].Descendants("p").Single().InnerText ?? "DSA";
var tds = divs[0].Descendants("td");
int info = 0;
var chapters = htmlDoc.DocumentNode.Descendants("div").Where(x => x.Attributes.Contains("id") &&
x.Attributes["id"].Value.Contains("chapters")).ToArray();
var chapterUi = chapters[0].Descendants("ul").Where(x => x.Attributes.Contains("class") &&
x.Attributes["class"].Value.Contains("chlist"));
foreach (var li in chapterUi)
{
var liChapter = li.Descendants("li");
foreach (var h3tag in liChapter)
{
var chapterH3 = h3tag.Descendants("a").ToArray();
SingleManagFox chapterData = new SingleManagFox();
chapterData.name = chapterH3[1].InnerHtml;
chapterData.url = chapterH3[1].GetAttributeValue("href", "0");
mangaDetails.chapters.Add(chapterData);
}
};
return mangaDetails;
}
UI code:
private async void mainBtn_Click(object sender, RoutedEventArgs e)
{
if (mangaList.SelectedItem != null)
{
test12((SingleManagFox)mangaList.SelectedItem);
}
}
private async void test12(SingleManagFox selectedManga)
{
selectedManga = (SingleManagFox)mangaList.SelectedItem;
MangaDetails mangaDetails = new MangaDetails(selectedManga);
MangaDetailsModel mdm = await mangaDetails.ParseMangaDescriptionPage();
txtMangaArtist.Text = mdm.artisName;
txtMangaAuthor.Text = mdm.authorName;
chapterList.ItemsSource = mdm.chapters;
}
Sorry if its trivial but i cannot figure it out myself.
When going async you need to try to go async all the way and avoid mixing blocking calls with async calls.
You are using async void in the event handler with no await.
Try to avoid async void unless it is an event handler. test12 should be updated to return Task and awaited in the event handler mainBtn_Click.
private async void mainBtn_Click(object sender, RoutedEventArgs e) {
if (mangaList.SelectedItem != null) {
await test12((SingleManagFox)mangaList.SelectedItem);
}
}
private async Task test12(SingleManagFox selectedManga) {
selectedManga = (SingleManagFox)mangaList.SelectedItem;
MangaDetails mangaDetails = new MangaDetails(selectedManga);
MangaDetailsModel mdm = await mangaDetails.ParseMangaDescriptionPage();
txtMangaArtist.Text = mdm.artisName;
txtMangaAuthor.Text = mdm.authorName;
chapterList.ItemsSource = mdm.chapters;
}
Also consider updating the web call to use HttpClient if available.
class ParseOneManga {
public async Task<string> GetMangaDescriptionPageAsync(string detailUrl) {
using (var client = new HttpClient()) {
string s = await client.GetStringAsync(detailUrl);
return s;
}
}
}
Reference: - Async/Await - Best Practices in Asynchronous Programming
Quite often people think that async-await means that multiple threads are processing your code at the same time. This is not the case, unless you explicitly start a different thread.
A good metaphore that helped me a lot explaining async-await is the restauran metaphor used in this interview with Eric Lippert. Search somewhere in the middle for async-await.
Eric Lipperts compares async-await processing with a cook who has to wait for his water to boil. Instead of waiting, he looks around if he can do other things instead. When finished doing the other thing, he comes back to see if the water is boiling and starts processing the boiling water.
The same is with your process. There is only one thread busy (at a time). This thread keeps processing until he has to await for something. This something is usually a fairly long process that is processed without using your CPU core, like writing a file to disk, loading a web page, or querying information from an external database.
Your thread can only do one thing at a time. So while it is busy calculating something, if can't react on operator input and your UI freezes, until the calculations are done. Async await will only help if there are a lot of times your thread would be waiting for other processes to complete
If you call an async function, you are certain that somewhere in that function is an await. In fact, if you declare your function async, and your forget to await in it, your compiler will warn you.
When your call meets the await in the function, your thread goes up its call stack to see if it can do other things. If you are not awaiting, you can continue processing, until you have to await. The thread goes up its call stack again to see if one of the callers is not awaiting etc.
async Task ReadDataAsync()
{
// do some preparations
using (TextReader textReader = ...)
{
var myReadTask = textReader.ReadToEndAsync();
// while the textReader is waiting for the information to be available
// you can do other things
ProcessSomething();
// after a while you really need the results from the read data,
// so you await for it.
string text = await MyReadTask;
// after the await, the results from ReatToEnd are available
Process(text);
...
There are some rules to follow:
an async function should return Task instead of void and Task<TResult> instead of TResult
There is one exception: the async event handler returns void instead of Task.
Inside your async function you should await somehow. If you don't await, it is useless to declare your function async
The result of await Task is void, and the result of await Task<TResult> is TResult
If you call an async function, see if you can do some processing instead of waiting for the results of the call
Note that even if you call several async functions before awaiting for them, does not mean that several threads are running these functions synchronously. The statement after your first call to the async function is processed after the called function starts awaiting.
async Task DoSomethingAsync()
{
var task1 = ReadAsync(...);
// no await, so next statement processes as soon as ReadAsync starts awaiting
DoSomeThingElse();
var task2 = QueryAsync(...);
// again no await
// now I need results from bothtask1, or from task2:
await Task.WhenAll(new Task[] {task1, task2});
var result1 = Task1.Result;
var result2 = Task2.Result;
Process(result1, result2);
...
Usually all your async functionality is performed by the same context. In practice this means that you can program as if your program is single threaded. This makes the look of your program much easier.
Another article that helped me a lot understanding async-await is Async-Await best practices written by the ever so helpful Stephen Cleary
I have a webservice that loads up some plugins (dlls) and calls their Process method. One of the plugins takes a list of members and ensures that they are all included in a MailChimp list.
Here is the code that adds the users to the MailChimp group.
private async Task AddMCUsers(List<Member> _memberList)
{
using (var http = new HttpClient())
{
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user:password");
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", creds);
string memberURI = string.Format(#"{0}lists/{1}/members", _baseURI, _memberGroupId);
var jss = new JavaScriptSerializer();
foreach (var user in _memberlist)
{
var _addStatus = "";
try
{
var content = jss.Serialize(new MCPost()
{
email_address = user.Email,
status = "subscribed",
merge_fields = new MCMergeFields()
{
FNAME = user.Firstname,
LNAME = user.Lastname
}
});
using(var result = await http.PostAsync(memberURI, new StringContent(content,Encoding.UTF8, "application/json")))
{
var resultText = await result.Content.ReadAsStringAsync();
if(result.IsSuccessStatusCode)
{
_addStatus = "Success";
var _returnedUser = jss.Deserialize<MCMember>(resultText);
//Store new user's id
user.ServiceId = _returnedUser.id;
}
else
{
_addStatus = "Fail";
}
}
}
catch {
_addStatus = "Error";
}
LogEvent("Add User - " + _addStatus, string.Format("Id: {0} - {1} {2} (Account: {3}) : {4}", user.Id, user.Firstname, user.Lastname, user.AccountId, user.Email));
}
}
}
In normal procedural code, this wouldn't be a problem. However, the only Post method available on the httpClient was PostAsync. Being fairly new to the async/await stuff, I'm not sure the ramifications on the rest of my code ... particularly as it relates to my attempt to reuse the httpClient instead of instantiating a new one for each http call.
I'm not sure what happens with await when its wrapped in a foreach like I have. Will I run into issues with reusing the httpClient to make repeated calls when running asynchronously?
My other question is, what is actually going to be returned. IOW, my understanding is that await returns a Task. However, here, I'm looping through the list and making multiple calls to await PostAsync. My method returns a Task. But which task gets returned? If my calling method needs to wait for completion before moving on, what does its call look like?
private void Process()
{
//Get List
var task = AddMCUsers(list);
task.Wait();
//Subsequent processing
}
I've read that you should use Async all the way. Does this mean my calling method should look more like this?
public async Task Process()
{
//Get list
...
await AddMCUsers(list);
//Other processing
}
Thanks to whatever help you can offer on this.
In normal procedural code, this wouldn't be a problem.
The whole point of async/await is to write asynchronous code in a way that looks practically identical to "normal" synchronous code.
Being fairly new to the async/await stuff, I'm not sure the ramifications on the rest of my code ... particularly as it relates to my attempt to reuse the httpClient instead of instantiating a new one for each http call.
HttpClient was intended to be reused; in fact, it can be used for any number of calls simultaneously.
I'm not sure what happens with await when its wrapped in a foreach like I have.
One way to think of it is that await "pauses" the method until its operation completes. When the operation completes, then the method continues executing. I have an async intro that goes into more detail.
Will I run into issues with reusing the httpClient to make repeated calls when running asynchronously?
No, that's fine.
IOW, my understanding is that await returns a Task.
await takes a Task. It "unwraps" that task and returns the result of the task (if any). If the task completed with an exception, then await raises that exception.
My method returns a Task. But which task gets returned?
The Task returned from an async method is created by the async state machine. You don't have to worry about it. See my intro for more details.
If my calling method needs to wait for completion before moving on, what does its call look like? ... I've read that you should use Async all the way. Does this mean my calling method should look more like this?
Yes, it should look like your second snippet:
public async Task ProcessAsync()
{
//Get list
...
await AddMCUsers(list);
//Other processing
}
The only thing I changed was the Async suffix, which is recommended by the Task-based Asynchronous Pattern.
in your code you should be fine with reusing the HttpClient. What async / await is allow the code to release the execution thread to prevent locking a cpu thread while waiting for the web response. It also releases control back to the caller. When releasing code back to the caller it means that if your Process function does not await your AddMCUsers, Process could finish before AddMCUsers (useful in fire and forget situations to not await a method).
What async/await do not do is affect the logical flow of an individual method. When you await an async web call the execution is paused and then resumed at the same point once the web call returns. There is also thread context tracking and the code resumes in the same context (ie. UI thread or background thread depending on the parent) by default, but this can be changed if needed.
At some point in your code you may want to have a method that blocks until your async code competes and that is where you will want your Task.Wait() call to block execution. If all you use is awaits then it is possible for your program to end before your task competes. See the code example below.
class Program
{
static void Main(string[] args)
{
Task waitForMe = Task.Run(() => waitAsync());
}
static async Task waitAsync()
{
await Task.Delay(5000);
}
}
in the sample with out a Task.Wait call to block the Main method the program will end before the 5 second wait is complete. Having a main method of the following will cause the program to wait for 5 seconds before exiting:
static void Main(string[] args)
{
Task waitForMe = Task.Run(() => waitAsync());
waitForMe.Wait();
}
I have an application that communicates with a serial port. I am trying to make it so it doesn't freeze the UI when a series of reads are performed. I initially used a thread, but in the long run that isn't going to work well (the code is much more complicated than what is below). I am trying to figure out async/await, and here is what I have tried so far.
I have a click handler and related methods (details, including parameters to some of the methods, removed for simplicity) as follows:
private void cmdOpenModPort_Click(object sender, EventArgs e) {
// stuff
CheckBoardsAsync();
// stuff
}
private async void CheckBoardsAsync() {
// Read from a serial port with different settings.
for (byte iTryAdrs = 125; iTryAdrs <= 144; iTryAdrs++) {
ushort[] registers = await mt.ReadRegistersChkSeeBrdAsync(iTryAdrs);
if (registers != null) {
// Update UI based on registers
} else {
// Update UI
}
}
}
public async Task<ushort[]> ReadRegistersChkSeeBrdAsync(byte b) {
// can't await because the method is not async.
return ReadRegistersChkSeeBrd(b);
}
public ushort[] ReadRegistersChkSeeBrd(byte b) {
try {
// read from serial port. NOT an asynchronous method and it is
// calling a method from a package so I cannot control this.
return master.ReadHoldingRegisters(b);
} catch (Exception) {
return null;
}
}
This doesn't work because the click handler doesn't finish until CheckBoardsAsync finishes. I want the call to CheckBoardsAsync to be asynchronous, but that method makes a series of asynchronous calls and doesn't itself need to return anything, so there isn't a value to wait for. Since it doesn't return anything, I can't await it.
So what is the proper way to deal with this? My understanding is that I should not use threads in this case because I am waiting for I/O, and there is hardly any computation. So this seems like the correct approach. Do I just fake it and have the CheckBoardsAsync return something I don't care about? That seems weak.
I hope I have been clear enough.
You should be getting a warning for having a async method that has no await calls in it. Marking a method as async doesn't automatically make it asynchronous, it just allows the use of the await keyword. Here you have CPU bound work, no IO bound work, so what you want to do is perform that work in another thread (which can be done using Task.Run) and then asynchronously wait on that.
The change is easy enough, just remove ReadRegistersChkSeeBrdAsync entirely and replace:
ushort[] registers = await mt.ReadRegistersChkSeeBrdAsync(iTryAdrs);
with
ushort[] registers = await Task.Run(() =>
mt.ReadRegistersChkSeeBrd(iTryAdrs));
It would also be better practice to remove the CheckBoardsAsync method entirely and have the body of that method simply be the body of cmdOpenModPort_Click. (Or, for that matter, just attach CheckBoardsAsync as the click handler.)
You need to make it return Task (not Task<T>).
You can then await it even though it doesn't return a value.