I'm using C# on Unity and Firebase for this project, but I'm not sure that this is a framework problem, more like async-approach-trouble.
I've a method to write data on my firebase database (Firestore), this is the method inside my Database class:
public async void AsyncAddData()
{
User newUser = new User()
{
isConfigured = false,
tag = userName,
accessToken = ""
};
DocumentReference docRef = db.Collection("users").Document(newUser.tag);
await docRef.SetAsync(newUser);
Debug.Log("Added new user " + newUser.tag);
}
This method works perfectly when called from my MainController like: Database.AsyncAddData().
The troubles begin when instead of creating inside the async method the new User variable, I try to pass from my MainController an already existing User variable like Database.AsyncAddData(this.currentUser), so the async method now looks like:
public async void AsyncAddData(User newUser)
{
DocumentReference docRef = db.Collection("users").Document(newUser.tag);
await docRef.SetAsync(newUser);
Debug.Log("Added new user " + newUser.tag);
}
This method CRASHES the entire software. My thoughs after experimenting a little bit is that passing a "sync" variable like currentUser to an async method, is not loable. I think so cause if I do Database.AsyncAddData(new User() { tag = currentUserLoged.tag }), it works.
So now my question is...
How can I pass external variables to an async method?
Already readed related posts and questions:
Async Await Issue for methods with parameters
Unity crashes on async programming with Firebase database
Async await best practices in asynchronous programming
Use "Task" as return type of async function.
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/async-return-types#void-return-type
The Task return type prevents removal of newUser by "caller" method until async ends :
you call asyncmethod with newUser from "caller" method
await suspends processing async method and starts processing "caller" method
the newUser is cleaned at the end of "caller" method
then await ends and async method starts processing
in this case 'newUser' is null and throw exception
Related
I am using .net core 6.0. I am getting this error when I am calling same method from different places. I tried calling this method GetEmployeeByEmployeeNumber from inside Index and I dont get any error and the method returns the object employee with value values populated in employee
public async Task<IActionResult> Index()
{
EmployeeInfo employee = await _employeeService.GetEmployeeByEmployeeNumber(up.EmployeeId);
PopulatePDFDoc();
return View();
}
public async Task<EmployeeInfo?> GetEmployeeByEmployeeNumber(string employeeId)
{
List<int> emplyoeeInfoId = new List<int>();
UserPrincipal up = utilities.UserADIdentity.GetADUserInfo(WindowsIdentity.GetCurrent().Name.ToString());
emplyoeeInfoId = _ackContext.EmployeeInfos.Where(e => e.EmployeeNumber == employeeId).OrderBy(e => e.DateFormFilled).Select(e => e.EmployeeInfoId).ToList();
var employee = await _ackContext.EmployeeInfos.Include(e => e.EmergencyInfos.Where(p => p.EmployeeInfoId.Equals(emplyoeeInfoId.LastOrDefault()))).Where(e=>e.EmployeeInfoId.Equals(emplyoeeInfoId.LastOrDefault())).FirstOrDefaultAsync();
return employee;
}
If I call the same method GetEmployeeByEmployeeNumber from inside PopulatePDFDoc(); then I get an error saying System.InvalidOperationException: 'Invalid operation. The connection is closed.'
below is my PopulatePDFDoc
public async void PopulatePDFDoc()
{
AckPackage.Data.PDFPopulate.DocPDF doc = new Data.PDFPopulate.DocPDF();
EmployeeInfo employee = await _employeeService.GetEmployeeByEmployeeNumber(up.EmployeeId);
}
below is the screen shot of the error:
I am new to .net core. Any help on this will be highly appreciated.
You need to await the call to PopulatePDFDoc() inside the Index method.
Like this:
await PopulatePDFDoc();
Always use await when calling an async method!
The reason you’re getting a “connection closed” error, is because the call to PopulatePDFDoc() is not being “awaited”, and the server request ends before the asynchronous method can run.
Also, PopulatePDFDoc() should return Task instead of void, like this:
public async Task PopulatePDFDoc()
Another thing I noticed that may cause you issues is your _ackContext which looks like it's a class-wide member variable based on the snippet you shared, meaning that same context-instance is shared between multiple methods.
However the context itself is actually not "thread safe", as can be read in Microsofts documentation here: https://learn.microsoft.com/en-us/ef/ef6/fundamentals/working-with-dbcontext which means that if multiple async methods use the same context - which they do in your example - you may run into issues.
The recommended approach is to create the context when you need it, and use the using syntax to make sure it's disposed properly after your work is finished. Like this:
using (var ackContext = new EmployeeContext())
{
// Perform data access using the context here
}
Try using that in your GetEmployeeByEmployeeNumber method and see if it helps as well :)
I am using a client to interact with CloudMQTT API. I am trying to create a user but after trying the code provided below, I was not able to create a user. When using the code provided within the Github repository for this project, I noticed that I am unable to make use of a ShouldThrow() method (apparently it should be provided by Fluent Assertions).
I did find a post on StackOverflow which looked very similar to the problem I am having. In the question is mentioned that FluentAssertions does not support async methods. In the example code for the client, however, I can see that the ShouldThrow() method is used regardless of this fact.
How could I get the ShoudldThrow() to work or do I even need it to work (because I think it is only supposed to be required in this code if you are applying unit testing)?
This is what a tried so far:
public static async void CreateCloudUser(ICloudMqttApi client)
{
var users = await client.GetUsers();
Console.WriteLine($"Creating a user. Current users available: {users.Count}");
var expectedUser = new NewUser
{
Password = $"{Guid.NewGuid()}",
Username = $"staging-{Guid.NewGuid()}",
};
var createUserResponse = await client.CreateUser(expectedUser);
createUserResponse.IsSuccessStatusCode.Should().BeTrue();
var actual = await client.GetUser(expectedUser.Username);
actual.Should().NotBeNull();
actual.Username.Should().Be(expectedUser.Username);
//users.Should().Contain(u => u.Username == expectedUser.Username); // <-- This throws an exception as well, but not of importance for this specific question.
Func<Task> verifyUser = async () => await client.GetUser(expectedUser.Username);
verifyUser.ShouldThrow<ApiException>() // <-- Not recognized
.And.StatusCode.Should().Be(HttpStatusCode.NotFound);
Console.WriteLine($"Created a user. Current users available: {users.Count}");
}
The client is defined in the way as provided in the documentation for the client right before calling the method:
var client = CloudMqttApi.GetInstance("username", "password");
The user count will result in the same number before and after executing the method (which obviously should have incremented).
Given the asynchronous nature of the shown code, the syntax should be
//...
var deleteResponse = await client.DeleteUser(expectedUser.Username);
deleteResponse.IsSuccessStatusCode.Should().BeTrue();
Func<Task> verifyUser = async () => await client.GetUser(expectedUser.Username);
var exceptionAssertion = await verifyUser.Should().ThrowAsync<ApiException>();
exceptionAssertion.And.StatusCode.Should().Be(HttpStatusCode.NotFound);
//...
Reference FluentAssertions: Exceptions
Also avoid using async void. Have the function return Task
public static async Task CreateCloudUser(ICloudMqttApi client) {
//...
}
Reference Async/Await - Best Practices in Asynchronous Programming
I'm writing a xamarin forms app and I'm using an API that I created. I've followed a tutorial to consume the Api but the code after the async operation never gets executed, it jumps out to the main function.
The code is exactly like the one in the tutorial I've been following. I didn't find any info since there is no error message.
private async void ChecarCredenciales(string username, string password)
{
HttpClient client = new HttpClient();
var url = "http://localhost:57008/api/operadores/" + username;
var response = await client.GetStringAsync(url).ConfigureAwait(false);
Lecturista = JsonConvert.DeserializeObject<Operadores>(response);
}
The JsonConvert.DeserializeObject never gets executed so the Lecturista variable never gets initialized.
Thanks in advance.
First as other already commented change your method to be async Task rather like private async Task ChecarCredenciales(string username, string password){
Second in your await block you are saying to continue on a Threadpool thread context rather on the same synchronization context by doing ConfigureAwait(false);. I would suggest you continue on the same context since on the next step you are requiring the resultant data
var response = await client.GetStringAsync(url);
Lecturista = JsonConvert.DeserializeObject<Operadores>(response);
In my service layer I wanted to fire multiple methods asynchronously and await for their results.
I tried with one at a time and it is erroring out.
In my service class I called the method like
var _validChapterCodesTask = gd.validateChapterCodeDetails(_input1);
await Task.WhenAll(_validChapterCodesTask);
var _chapterCodeResult = await _validChapterCodesTask;
And in DAL class the method definition looks like
public async Task<IEnumerable<ChapterCodeValidationOutput>> validateChapterCodeDetails(GroupMembershipValidationInput gmvi)
{
Repository rep = new Repository();
if (!gmvi._chapterCodes.All(x => x.Equals("")))
{
var _validChapterCodes = await rep.ExecuteStoredProcedureAsync<Entities.Upload.ChapterCodeValidationOutput>(SQL.Upload.UploadValidation.getChapterCodeValidationSQL(gmvi._chapterCodes),null);
return _validChapterCodes;
}
else
return new List<ChapterCodeValidationOutput>();
}
Error message
Error 208 The await operator can only be used within an async method. Consider marking this method with the async modifier and changing its return type to Task<ARC.Donor.Business.Upload.GroupMembershipValidationOutput>. C:\Users\m1034699\Desktop\Stuart_Upgrade_2.1_New Approach\Stuart_Export_Upload_v2.1\Stuart Web Service\ARC.Donor.Service\Upload\UploadValidationServices.cs 34 13 ARC.Donor.Service
in lines
await Task.WhenAll(_validChapterCodesTask);
var _chapterCodeResult = await _validChapterCodesTask;
What am I doing wrong ?
The error message is very explicit. It is telling you that you're Service class method is incorrectly attempting to use the async keyword. In order to fix this, you should be using "Async all the way" as defined in Stephen Cleary's post on MSDN as a best practice.
For example, if you're service class has a method body that is working with Task or Task<T>, or attempting to use the await keyword the method signature for this corresponding method body must be async (as the async keyword enables a method to use the await keyword). Additionally this method itself should also be Task or Task<T> returning, with very few exceptions to that rule.
Personally, I would alter your DAL class to simply return the operation that represents the asynchronous work without actually awaiting it. If you think about it, the body of the validateChapterCodeDetails method does not actually need to do anything with the results, it just needs to return them (instead of materializing them there). Consider the following:
public Task<IEnumerable<ChapterCodeValidationOutput>>
validateChapterCodeDetails(GroupMembershipValidationInput gmvi)
{
var rep = new Repository();
return gmvi._chapterCodes.All(x => x.Equals(""))
? new List<ChapterCodeValidationOutput>()
: rep.ExecuteStoredProcedureAsync
<Entities.Upload.ChapterCodeValidationOutput>
(SQL.Upload.UploadValidation
.getChapterCodeValidationSQL(gmvi._chapterCodes),null)
}
Since your Task<IEnumerable<ChapterCodeValidationOutput>> variable has already been awaited, you can access the .Result property to get what you're looking for.
Then in your Service class your method would look like this:
public async Task ConsumeAsync()
{
var _validChapterCodesTask = gd.validateChapterCodeDetails(_input1);
await Task.WhenAll(_validChapterCodesTask);
var _chapterCodeResult = _validChapterCodesTask.Result;
// Do something with it...
}
Here is a .NET fiddle that should help to exemplify this for you better.
NOTE
I would also caution using IEnumerable as it pertains to your repo, you should ensure that the results from the Database are not occurring via deferred execution, otherwise you risk the potential for connection issues, i.e.; unintentionally leaving a connection open, or not properly closing one.
In the Service class where you call
var _validChapterCodesTask = gd.validateChapterCodeDetails(_input1);
await Task.WhenAll(_validChapterCodesTask);
var _chapterCodeResult = await _validChapterCodesTask;
add a async to the method signature where await Task.WhenAll(_validChapterCodesTask); is called. And when this method also has a return type you have to wrap this type in a Task like the exception shows:
Task<ARC.Donor.Business.Upload.GroupMembershipValidationOutput>
E.g. if you have the following method:
public GroupMemberShipValidationOutput validate(..) {
...
await Task.WhenAll(_validChapterCodesTask);
...
}
You have to change the method signature to:
public async Task<GroupMemberShipValidationOutput> validate(..) {
...
await Task.WhenAll(_validChapterCodesTask);
...
}
I have a Controller Action method to Save user Details like below.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
return Json(new { Success = String.IsNullOrEmpty(result) });
}
So far there is no issue in above 4 lines function.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
new MailController().CreateUser(user.userDetails); //<==This is problem line of code.
}
and Below is my Mail Controller.
public class MailController : MailerBase
{
public void CreateUser(ViewModel.VM_User user)
{
To.Add(user.EmailAddress);
From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
Subject = "Hi";
Email("CreateUser", user).DeliverAsync();
}
}
in the above code, I am facing problem when I execute the Email Sending code. I get below error message.
An asynchronous module or handler completed while an asynchronous
operation was still pending
Please suggest the corrective action !!
This is because async methods track their completion, even async void. They do this by registering with the SynchronizationContext when starting and marking the operation complete when returning. ASP.NET tracks all created operations and requires them to all be completed before your action returns, otherwise it returns an error to the HTTP client. If you need to run a “fire and forget” method, you must manually avoid launching it on the ASP.NET SynchronizationContext. For example, await Task.Run(() => CallFireAndForget()) (await so that you wait for the synchronous portion to run on the thread pool. This works because CallFireAndForget() is async void. To fire a method which returns a Task as fire-and-forget, you have to explicitly avoid returning the Task to Task.Run() like this: await Task.Run(() => { CallFireAndForgetAsync(); })).
Another way to get the same error message to show up should be to write this in an asynchronous action:
// BAD CODE, DEMONSTRATION ONLY!
AsyncOperationManager.CreateOperation();
If you use the AsyncOperation API, you have to ensure you call AsyncOperation.OperationCompleted() prior to allowing the action method to return.
In your case, if the mail sending is really intended to be a fire-and-forget task, you can do the following inside of CreateUser after changing its signature to async Task CreateUserAsync(ViewModel.VM_User user):
await Task.Run(() => Email("CreateUser", user).DeliverAsync());
and in your action:
await new MailController().CreateUserAsync(user.userDetails);
Ideally, you wouldn’t use Task.Run() for something like this. Instead, you can temporarily replace the current SynchronizationContext instead (NOTE WELL: Never await inside the try{}—instead, if you need to, store the Task in a local and await it after restoring the original SynchronizationContext (it may be safer to use a primitive such as SynchronizationContextSwitcher.NoContext() (source) which does the right thing)):
var originalSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
new MailController().CreateUser(user.userDetails);
}
finally
{
SynchronizationContext.SetSynchronizationContext(originalSynchronizationContext);
}
Does DeliverAsync return a Task? If it does, try this.
public class MailController : MailerBase
{
public async Task CreateUserAsync(ViewModel.VM_User user)
{
To.Add(user.EmailAddress);
From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
Subject = "Hi";
await (Email("CreateUser", user).DeliverAsync());
}
}
And then in your controller, await the task returned by CreateUserAsync.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
await (new MailController().CreateUserAsync(user.userDetails));
return Json(new { Success = String.IsNullOrEmpty(result) });
}
note: If your goal is to make sending the email a background fire and forget operation, this is not it.