I have a xUnit test to quickly check if my API behaves correctly. It's not mocked, just follows some scenarios on flows.
[Fact]
public async Task HappyPath()
{
User = await authHelper.CreateUser();
ContentCreator = await authHelper.CreateUser(isContentCreator: true);
RequestId = await Create();
await User_Should_See_Request_Before_Approval();
await Content_Creator_Should_Not_See_Request_Before_Approval();
await Approve();
await Second_Approve_Should_Fail();
await List();
await Offer();
await Offer();
await Offer();
//await Pay();
// Cleanup
if (shouldCleanup)
{
await Cleanup();
}
}
All these methods are doing API calls with proper generated values.
When I debug the test, it runs normally and I can get the green tick.
However, when I run the test (CTRL + R, T), even as a single, it keeps running indefinitely. While it runs I can observe all endpoints are called, and resources cleaned up. But the test just keeps running.
I'm not observing anything in the output (was hunting for exceptions) and on Visual Studio 2022 17.4.4.
Related
I have an async member which ultimately needs to invoke some UI updates, after getting some data from a server.
I think I need something like BeginInvokeOnMainThread, or Dispatcher.Invoke, but neither of these appear to be recognized in the Uno context.
Here's the essence of what I have:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
BeginInvokeOnMainThread () =>
{
... update the UI ...
});
}
But I get the error CS0103 The name 'BeginInvokeOnMainThread' does not exist in the current context UnoTest.Droid, UnoTest.UWP, UnoTest.Wasm
BeginInvokeOnMainThread / Dispatcher.Invoke / Control.Invoke were never a good idea.
Uno should have a SynchronizationContext that is automatically used by await, so manual thread marshaling should not be necessary:
public async Task LoadList()
{
...
// get data
Uri uri = new Uri("https://...");
response = await httpClient.GetAsync(uri);
// display
... update the UI ...
}
I need to insert data to my database using InsertData(). Data is being fetched using another service. The below code is executing one after one. What is the best approach to execute this in parallel.
var taskslist = new List<Task>();
taskslist.Add(this.database.InsertData("data1", await this.service.GetData1()));
taskslist.Add(this.database.InsertData("data2", await this.service.GetData2()));
taskslist.Add(this.database.InsertData("data3", await this.service.GetData3()));
await Task.WhenAll(taskslist);
You could wrap your calls to await in another async method:
async Task GetAndInsert(string key, Func<Task<IList<model>>> getFunc)
=> await database.InsertData(key, await getFunc());
Then each can be run simultaneously:
var taskslist = new List<Task>();
taskslist.Add(GetAndInsert("data1", service.GetData1));
taskslist.Add(GetAndInsert("data2", service.GetData2));
taskslist.Add(GetAndInsert("data3", service.GetData3));
await Task.WhenAll(taskslist);
If you don't need to use the Tasks after they have completed, you could even pass them straight to Task.WhenAll rather than creating a list:
await Task.WhenAll(
GetAndInsert("data1", service.GetData1),
GetAndInsert("data2", service.GetData2),
GetAndInsert("data3", service.GetData3));
I was able to convert my EchoBot to interact with QnAMaker as per instructions here on my local development system but when I publish the same using kudu repo (tried using Azure DevOps service Ci/CD pipeline but it does not work [in preview] because after deployment the bot just hangs on portal and never able to test it on web chat.. so gave up and used recommended kudu repo), I do not get the correct answer to my response. For every question I send, it is unable to detect the QnAMaker service. And I am returning error message from the code that says no QnaMaker answer was found.
How do I troubleshoot to identify the cause of this?
My bot file seems to be working fine locally and I am able to get the answer from QnAMaker locally but not after publishing the code to my Web App Bot in Azure.
I feel like Botframework V4 (using .net) is not very straight forward and the instruction on the portal (document) is still kind of evolving or sometime incomprehensible.
Here is the snapshot from my emulator while testing the chat locally:
And here is the snapshot of production endpoint (using the same questions on portal) with my error msg from OnTurnAsync function:
My .bot has all the services defined and local bot is working fine.
This is the code in my ChatBot class:
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// Handle Message activity type, which is the main activity type for shown within a conversational interface
// Message activities may contain text, speech, interactive cards, and binary or unknown attachments.
// see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Get the conversation state from the turn context.
var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());
// Bump the turn count for this conversation.
state.TurnCount++;
// Set the property using the accessor.
await _accessors.CounterState.SetAsync(turnContext, state);
// Save the new turn count into the conversation state.
await _accessors.ConversationState.SaveChangesAsync(turnContext);
// Echo back to the user whatever they typed.
//var responseMessage = $"Turn {state.TurnCount}: You sent '{turnContext.Activity.Text}'\n";
//await turnContext.SendActivityAsync(responseMessage);
// QnAService
foreach(var qnaService in _qnaServices)
{
var response = await qnaService.GetAnswersAsync(turnContext);
if (response != null && response.Length > 0)
{
await turnContext.SendActivityAsync(
response[0].Answer,
cancellationToken: cancellationToken);
return;
}
}
var msg = "No QnA Maker answers were found. Something went wrong...!!";
await turnContext.SendActivityAsync(msg, cancellationToken: cancellationToken);
}
else
{
await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
}
}
I've been programming a lot with the MS Bot Framework but I can't seem to make this seemingly trivial part work: sending a Typing activity.
This fails every time:
"Object reference not set to an instance of an object"
What is going on?
Try it like this (if inside a dialog):
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var activity2 = activity.CreateReply();
activity2.Type = ActivityTypes.Typing;
await context.PostAsync(activity2);
context.Wait(MessageReceivedAsync);
}
This is most likely because you are using MakeMessage() but not actually setting any of the fields it needs, you are only setting Type so some property in the activity created is throwing the null object reference error.
I'm having some trouble understanding the in's and out's of "continueOnCapturedContext" from a .NET v4.6 WebAPI 2 standpoint.
The problem I'm having is there doesn't appear to be any difference between ConfigureAwait(true) and ConfigureAwait(false).
I've put together a sample app that demonstrates what's happening:
public async Task<IHttpActionResult> Get(bool continueOnContext)
{
int beforeRunningExampleThreadId = Thread.CurrentThread.ManagedThreadId;
int runningExampleThreadId = await ExecuteExampleAsync(continueOnContext).ConfigureAwait(continueOnContext);
int afterRunningExampleThreadId = Thread.CurrentThread.ManagedThreadId;
return Ok(new
{
HasSyncContext = SynchronizationContext.Current != null,
ContinueOnCapturedContext = continueOnContext,
BeforeRunningExampleThreadId = beforeRunningExampleThreadId,
RunningExampleThreadId = runningExampleThreadId,
AfterRunningExampleThreadId = afterRunningExampleThreadId,
ResultingCulture = Thread.CurrentThread.CurrentCulture,
SameThreadRunningAndAfter = runningExampleThreadId == afterRunningExampleThreadId
});
}
private async Task<int> ExecuteExampleAsync(bool continueOnContext)
{
return await Task.Delay(TimeSpan.FromMilliseconds(10)).ContinueWith((task) => Thread.CurrentThread.ManagedThreadId).ConfigureAwait(continueOnContext);
}
For "/Test?continueOnContext=true", this returns me:
{"HasSyncContext":true,"ContinueOnCapturedContext":true,"BeforeRunningExampleThreadId":43,"RunningExampleThreadId":31,"AfterRunningExampleThreadId":56,"ResultingCulture":"fr-CA","SameThreadRunningAndAfter":false}
So you can see I have a Sync context, I'm doing ConfigureAwait(true) and yet the thread isn't "continuing" in any way - a new thread is assigned before, while running and after running the asynchronous code. This isn't working the way I would expect - have I some fundamental misunderstanding here?
Can someone explain to me why in this code ConfigureAwait(true) and ConfigureAwait(false) are effectively doing the same thing?
UPDATE -
I figured it out and have answered below. I also like the answer from
#YuvalShap. If you're stuck on this like I was I suggest you read both.
When an asynchronous handler resumes execution on legacy ASP.NET, the
continuation is queued to the request context. The continuation must
wait for any other continuations that have already been queued (only
one may run at a time). When it is ready to run, a thread is taken
from the thread pool, enters the request context, and then resumes
executing the handler. That “re-entering” the request context involves
a number of housekeeping tasks, such as setting HttpContext.Current
and the current thread’s identity and culture.
From ASP.NET Core SynchronizationContext Stephen Cleary's blog post.
To sum up, ASP.NET versions prior to Core uses AspNetSynchronizationContext as the request context, that means that when you are calling ConfigureAwait(true) (or not calling ConfigureAwait(false)) you capture the context which tells the method to resume execution on the request context.
The request context keeps HttpContext.Current and the current thread’s identity and culture consistent but it is not exclusive to a specific thread the only limitation is that only one thread can run in the context at a time.
OK I figured it out, so I'll post an answer in case it will help others.
In .NET 4.6 WebAPI 2 - the "Captured Context" that we are continuing on isn't the thread, it's the Request context. Among other things, the Request Context knows about the HttpContext. When ConfigureAwait(true) is specified, we're telling .NET that we want to keep the Request Context and everything about it (HttpContext & some other properties) after the await - we want to return to the context that we started with - this does not take into account the thread.
When we specify ConfigureAwait(false) we're saying we don't need to return to the Request Context that we started with. This means that .NET can just return back without having to care about the HttpContext & some other properties, hence the marginal performance gain.
Given that knowledge I changed my code:
public async Task<IHttpActionResult> Get(bool continueOnContext)
{
var beforeRunningValue = HttpContext.Current != null;
var whileRunningValue = await ExecuteExampleAsync(continueOnContext).ConfigureAwait(continueOnContext);
var afterRunningValue = HttpContext.Current != null;
return Ok(new
{
ContinueOnCapturedContext = continueOnContext,
BeforeRunningValue = beforeRunningValue,
WhileRunningValue = whileRunningValue,
AfterRunningValue = afterRunningValue,
SameBeforeAndAfter = beforeRunningValue == afterRunningValue
});
}
private async Task<bool> ExecuteExampleAsync(bool continueOnContext)
{
return await Task.Delay(TimeSpan.FromMilliseconds(10)).ContinueWith((task) =>
{
var hasHttpContext = HttpContext.Current != null;
return hasHttpContext;
}).ConfigureAwait(continueOnContext);
}
When continueOnContext = true:
{"ContinueOnCapturedContext":true,"BeforeRunningValue":true,"WhileRunningValue":false,"AfterRunningValue":true,"SameBeforeAndAfter":true}
When continueOnContext = false:
{"ContinueOnCapturedContext":false,"BeforeRunningValue":true,"WhileRunningValue":false,"AfterRunningValue":false,"SameBeforeAndAfter":false}
So from this example you can see that HttpContext.Current exists before the asynchronous method and is lost during the asynchronous method regardless of the ConfigureAwait setting.
The difference comes in AFTER the async operation is completed:
When we specify ConfigureAwait(true) we get to come back to the Request Context that called the async method - this does some housekeeping and syncs up the HttpContext so it's not null when we continue on
When we specify ConfigureAwait(false) we just continue on without going back to the Request Context, therefore HttpContext is null