Forcing web request to wait for an event callback - c#

I have a web request (HttpRequest) which triggers a third library scanning method on my server that has an event handler attached to it:
scanner.OnScanComplete += scanner_OnScanComplete;
The web request will invoke scanner.Scan(files) but how can I force (or hook) the request to wait and get the results from scanner_OnScanComplete when the scan process is complete so it can return data to clients without having to send another web request to get this data?
void DoWork(HttpRequst request, var files)
{
var scanner = new Scanner()
scanner.OnScanComplete += scanner_OnScanComplete;
scan(files)
}
void scanner_OnScanComplete(object sender, EventArgs e)
{
var scanCompleted = true;
//Return scanCompleted somehow to the DoWork thread above
}

Do you have to use a HttpHandler or can you use other api's?
If you can use MVC4 or later then you can use an asynchronous Action Method to do this easily. Look here for an example of how to use them.
In addition to using an async Action Method you may need a way to await the event from the scanner. Using a Task Completion source as in this answer may be a good way to do that.

One way to do what you want is to store the completion of the task in a boolean member.
The boolean shall be marked volatile to avoid threading issues.
The risk of the approach is to lead to timeouts on client side if the scan processing is too long.
private volatile bool _finished;
void DoWork(HttpRequst request, var files)
{
var scanner = new Scanner();
scanner.OnScanComplete += scanner_OnScanComplete;
_finished= false;
scan(files)
while (!_finished) // wait for the scan completion
System.Threading.Thread.Sleep(1000); // avoid consuming 100% cpu
var scanData = Dothescanwork();
//Return scanData somehow to the DoWork thread above
}
void scanner_OnScanComplete(object sender, EventArgs e)
{
_finished= true;
}

Related

Proper way to implement my own async Worker Service(s) according to TAP pattern

I'm newbie in async. And making WPF app for scraping and API calls purposes. WPF's UI is needed only for service monitoring and settings control, so all services will run simultaneously in the background. And most of them is doing similar work.
For this one I need to implement strategy like this:
Start worker on threadpool
Worker must send request and process response from Website
2.1 If response processed and some new data appeared - raise an event
2.2 If request failed - handle an error
2.3 If there are many error percent for last x requests - stop worker
No matter the last request completed/failed/running we must send another request
Another request should be sent not earlier than the set delay but should not exceed the delay too much (as far as possible).
private _workTask;
private List<ScrapeParameters> _scrapeParams = new();
public event EventHandler<ScrapedEventArgs>? NewDataScraped;
//Can I run Worker like this?
public void RunWorker()
{
if(_workTask.IsCompleted)
_workTask = WorkAsync(_token)
}
private async Task WorkAsync(CancellationToken cancelToken)
{
List<Task> processTasks = new();
while(true)
{
if(cancelToken.IsCancellationRequested) return;
//Delay could be from 0.5 second to any value
var delayTask = Task.Delay(WorkerDelay);
var completedTasks = processTasks.Where(t => t.IsCompleted)
var setToHandle = new HashSet<Task>(completedTasks);
foreach(var task in setToHandle)
{
//Theoretical logic to handle errors and completion
if(task.IsFaulted)
HandleFaultedTask(task);
else
CountCompleted();
processTasks.Remove(task);
}
//Theoretical logic to obtain the desired parameters.
var currParameters = GetParameters();
processTasks.Add(ProcessAsync(currParameters, cancelToken));
await delayTask;
}
}
//This method usually takes around 2-4 seconds
private async Task ProcessAsync(ScrapeParameters parameters CancellationToken cancelToken)
{
//Some work with http requests
var response = await Client.GetAsync(parameters.ToUri());
...
//Processing response
...
if(newData != null)
NewDataScraped?.Invoke(new(newData));
}
Does my implementation matches the TAP pattern?
Especially I would like to focus on RunWorker() and setToHandle

C# WPF - ApplicationExit calling an API

I have to call an API when my WPF Applications is being closed.
I'm using Application_Exit event because I have to do this even closing all the windows, being shut down from Task Manager or Alt + F4.
I've tried several ways, but all the options has some issues (some of them are these):
OPTION A: Async event + await
private async void Application_Exit(object sender, ExitEventArgs e)
{
if (MonitorInstance.Instance.User != null)
{
Log.Information($"Disconnect starting");
var result = await _apiClient.Disconnect();
Log.Information($"Disconnect result: {result}");
}
}
This option just calls the HTTP CONNECT (which sometimes succeed and others is being cancelled) but not my PATCH request.
OPTION B: Blocking Thread with Result of Task.
private void Application_Exit(object sender, ExitEventArgs e)
{
if (MonitorInstance.Instance.User != null)
{
Log.Information($"Disconnect starting");
var result = _apiClient.Disconnect().Result;
Log.Information($"Disconnect result: {result}");
}
}
This effectively calls the API but the process continues running. Visual Studio continues showing that the App is running and this is not desired.
I think it's worth to mention that I'm calling the API (which uses SSL) using HttpClient which is being created in the Disconnect method and I'm using .NET Core 3.1.
My questions:
Is Application_Exit the proper event to do this?
How to program the Application_Exit event to fulfill all the scenarios?
Should I change the implementation for my call to do it directly without the HttpClient?
This a classic case of deadlock. Your code is running under a synchronization context, your await calls inside of _apiClient.Disconnect() try to resume on the UI thread, but the UI thread is blocked waiting synchronously on the task because of .Result.
There are multiple ways to get around that, but for your precise case I don't think it's useful to get fancy. Just use Task.Run to run your call in a safer context:
private void Application_Exit(object sender, ExitEventArgs e)
{
if (MonitorInstance.Instance.User != null)
{
Log.Information($"Disconnect starting");
var result = Task.Run(apiClient.Disconnect).Result;
Log.Information($"Disconnect result: {result}");
}
}

Allocate separate thread per event in FileSystemWatcher

I am developing a windows service that contains the FileSystemWatcher. Once file creates, I have to make some network calls to a web API.
Please look at below code lines.
watcher.Created += new FileSystemEventHandler((object source, FileSystemEventArgs e) => { ProcessCreateEvent(e); });
Event handler
private async void ProcessCreateEvent(FileSystemEventArgs e){
// make some network calls and do certain tasks
// network calls doing asynchronously
}
I did in deep about the FileSystemWatcher and I understand that it is not the good practice to handle the network call in ProcessCreateEvent method. So how can I allocate the separate thread for each file change?
Events already support async, so you can just do something like:
watcher.Created += this.ProcessCreateEvent;
private async void ProcessCreateEvent(object sender, FileSystemEventArgs e)
{
var result = await asyncApi.GetStuffAsync();
}
You don't need to spin up another Task unless the non-async stuff you are doing in the event handler is expensive.
I think it can simply be done by making ProcessCreateEvent asynchronous like this:
private async Task ProcessCreateEvent(FileSystemEventArgs e)
{
// make some network calls and do certain tasks
// network calls doing asynchronously
await Task.Run(async () => {
var client = new HttpClient();
var response = await client.GetAsync("http://localhost:54522/api/values");
var result = await response.Content.ReadAsStringAsync();
});
}

WebClient DownloadStringAsync blocked - never finished

I have specific problem with WebClient in my Windows Phone app (using MVVM)
private string _lastCurrencyRatesJson;
private bool _lastCurrencyRatesJsonLoaded = false;
private void GetLastCoursesFromApiAsync()
{
var uri = new Uri(string.Format(OperationGetLastCourses, AppSettings.ApiEndpoint, AppSettings.ApiKey));
var client = new WebClient { Encoding = Encoding.UTF8 };
client.DownloadStringCompleted += client_DownloadStringCompleted;
client.DownloadStringAsync(uri);
}
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
_lastCurrencyRatesJson = e.Result;
_lastCurrencyRatesJsonLoaded = true;
}
public List<CurrencyRate> GetLastCourses()
{
var worker = new Thread(GetLastCoursesFromApiAsync);
worker.Start();
while (!_lastCurrencyRatesJsonLoaded)
{
}
.....
The problem is that client_DownloadStringCompleted is never fired BUT when I change GetLastCourses this way:
public List<CurrencyRate> GetLastCourses()
{
var worker = new Thread(GetLastCoursesFromApiAsync);
worker.Start();
// whetever here, but any while...
client_DownloadStringCompleted is fired and data are obtained. It means, connectivity is ok.
I had very similar problems with DownloadStringTaskAsyn. Example:
private async Task<string> GetCoursesForDayFromApiAsJson(DateTime date)
{
var uri = new Uri(string.Format(OperationGetCoursesForDay, AppSettings.ApiEndpoint, AppSettings.ApiKey, date.ToString(DateFormat)));
var client = new WebClient { Encoding = Encoding.UTF8 };
return await client.DownloadStringTaskAsync(uri);
}
Again, at the line with await is application waiting for the data but the DownloadStringTaskAsync is never finished and my UI is still loading.
Any ideas what could be wrong?
SITUATION ONE DAY AGO
So, it looks that WP application is working just with one thread. It means, current thread have to be "finished" and then is DownloadStringTaskAsync finished and the code under the await executed. When I want to work with Task.Result I can not. Never.
When I create another Thread and I am trying to wait for thread completetion (using Join()), created Thread is never finsihed and the code after Join() is never executed.
There is any example on the Internet and I absolutely don't know, why exists some DownloadStringTaskAsync when it is not applicable.
You're blocking the UI thread by your while loop and at the same time, the DownloadStringCompleted event wants to execute on the UI loop. This causes a deadlock, so nothing happens. What you need to do is to let GetLastCourses() return (and whatever method calls that), so that the event handler can execute. This means that the code that handles the results should be in that event handler (not in GetLastCourses()).
With async-await, you didn't provide all of your code, but it's likely that you're encountering pretty much the same issue by calling Wait() or Result on the returned Task. If replace that with await, you code will work. Though that requires you to make all your code from GetCoursesForDayFromApiAsJson() up async.
I'd recommend to use the HttpClient class from Microsoft NuGet package and use the async/await downloading pattern rather than using event-based WebClient class:
Uri uri = new Uri(string.Format(OperationGetLastCourses, AppSettings.ApiEndpoint, AppSettings.ApiKey));
using (HttpClient client = new HttpClient())
{
string result = await client.GetStringAsync(uri);
}

Copy permissions / authentication to child threads...?

Here's something very weird I had noticed.
I'm writing a CRM 2011 Silverlight extension and, well, all is fine on my local development instance. The application uses OData to communicate, and uses System.Threading.Tasks.Task a lot to perform all the operations in the background (FromAsync is a blessing).
However, I decided to test my application in CRM 2011 Online and found, to my surprise, that it would no longer work; I would receive a Security Exception when ending retrieve tasks.
Using Fiddler, I found that CRM is trying to redirect me to the Live login page, which didn't make much sense, considering I was already logged in.
After some more attempts, I found that the errors were because I was accessing the service from a different thread than the UI thread.
Here's a quick example:
//this will work
private void button1_Click(object sender, RoutedEventArgs e)
{
var query = ctx.AccountSet;
query.BeginExecute((result) =>
{
textBox1.Text = query.EndExecute(result).First().Name;
}, null);
}
//this will fail
private void button2_Click(object sender, RoutedEventArgs e)
{
System.Threading.Tasks.Task.Factory.StartNew(RestAsync);
}
void RestAsync()
{
var query = ctx.AccountSet;
var async = query.BeginExecute(null, null);
var task = System.Threading.Tasks.Task.Factory.FromAsync<Account>(async, (result) =>
{
return query.EndExecute(result).First(); // <- Exception thrown here
});
textBox1.Dispatcher.BeginInvoke(() =>
{
textBox1.Text = task.Result.Name;
});
}
It seems almost obvious that I'm missing some fundamentals on how threads use permissions. Since using a separate thread is preferable in my case, is there any way to "copy" the permissions / authentication? Perhaps some sort of impersonation?
EDIT: In case anyone else is struggling with this, using other threads (or Task, as the case may be) is possible as long as query.BeginExecute(null, null); is executed on the UI thread. You need a way to retrieve the returned IAsyncResult back to the calling thread, but you can do that using a ManualResetEvent.
But I'd still like to know why the darned permissions / authentication isn't shared between the threads...
I am not quite sure, is this will help. But I found a description from by Jeffrey Richter page 770
"Like console applications, ASP.NET Web Form and XML Web Service applications allow
any thread to do whatever it wants. When a thread pool thread starts to process a client’s
request, it can assume the client’s culture (System.Globalization.CultureInfo), allowing
the Web server to return culture-specific formatting for numbers, dates, and times.5 In
addition, the Web server can assume the client’s identity (System.Security.Principal.
IPrincipal) so that the server can access only the resources that the client is allowed to
access. When a thread pool thread spawns an asynchronous operation, it will be completed
by another thread pool thread, which will be processing the result of an asynchronous operation.
While this work is being performed on behalf of the original client request, the culture
and identity information doesn’t flow to the new thread pool thread by default so any
additional work done on behalf of the client is now not using the client’s culture and identity
information. Ideally, we want the culture and identity information to flow to the other thread
pool threads that are still doing work on behalf of the same client."
And here is his example, I hope this will help.
private static AsyncCallback SyncContextCallback(AsyncCallback callback)
{
SynchronizationContext sc = SynchronizationContext.Current;
// If there is no SC, just return what was passed in
if (sc == null) return callback;
// Return a delegate that, when invoked, posts to the captured SC a method that
// calls the original AsyncCallback passing it the IAsyncResult argument
return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
}
protected override void OnMouseClick(MouseEventArgs e) {
// The GUI thread initiates the asynchronous Web request
Text = "Web request initiated";
var webRequest = WebRequest.Create("http://Wintellect.com/");
webRequest.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webRequest);
base.OnMouseClick(e);
}
private void ProcessWebResponse(IAsyncResult result) {
// If we get here, this must be the GUI thread, it's OK to update the UI
var webRequest = (WebRequest)result.AsyncState;
using (var webResponse = webRequest.EndGetResponse(result)) {
Text = "Content length: " + webResponse.ContentLength;
}
}
And here is what I am using in my application
public override void UpdateCanvas(object parameter)
{
Action<GraphPane> startToUpdate = StartToUpdate;
GraphPane selectedPane = Canvas.HostingPane.PaneList.Find(p => p.Title.Text.Equals(defaultPanTitle));
startToUpdate.BeginInvoke(selectedPane, FormSyncContext.SyncContextCallback(RefreshCanvas), selectedPane);
}
public static AsyncCallback SyncContextCallback(AsyncCallback callback)
{
// Capture the calling thread's SynchronizationContext-derived object
SynchronizationContext sc = SynchronizationContext.Current;
// If there is no SC, just return what was passed in
if (sc == null) return callback;
// Return a delegate that, when invoked, posts to the captured SC a method that
// calls the original AsyncCallback passing it the IAsyncResult argument
return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
}

Categories