aspnet core stop background running task - c#

I have an api method which runs in background (async) for ~6 mins.
I want to allow the user to cancel it from another api request.
For Ex.
/api/credits/post/25 will start the background task and returns OK response immediately.
/api/credits/stop/25 will stop running background task.
my current code.
API
[Route("api/[controller]")]
public class CreditController : Controller
{
private readonly CreditService _creditService;
public CreditController (CreditService creditService)
{
_creditService = creditService;
}
[HttpPost("[action]")]
public string Post(int id)
{
if (_creditService.Instances.Any(p => p.ID == id))
return "already running " + id;
_creditService.Post(id, (s,e) =>
{
// sending realtime changes to user using SignalR.
// so that they will see progress and whats running in background
});
return "started " + id;
}
[HttpPost("[action]")]
public string Stop(int id)
{
var instance = _creditService.Instances.SingleOrDefault(p => p.ID == id));
if (instance == null)
return id + " is not running";
instance.CancelToken.Cancel();
return id + " stoppped";
}
}
Service
public class CreditService
{
public static List<CreditService> Instances = new List<CreditService>();
public Status<object> Status = new Status<object>();
public CancellationTokenSource CancelToken = new CancellationTokenSource();
public Task Post(int id, PropertyChangedEventHandler handler = null)
{
Instances.Add(this);
if (handler != null) Status.PropertyChanged += handler;
return Task.Factory.StartNew(() =>
{
try
{
// long running task
}
catch (Exception e)
{
Status.Error = e.Message ?? e.ToString();
}
finally
{
if (handler != null) Status.PropertyChanged -= handler;
Status.Clear();
CancelToken.Dispose();
Instances.Remove(this);
}
});
}
}
This is working as expected. but i want to know is this a good approach or is there any other better alternative to this.
Also this will not work if i use load balancer and multiple instances of application. How do i fix this.

Related

FormFlow 'Help' Command and Scorables/GlobalMessage Handlers 'Help' Command

When a conversation is inside FormFlow and user types 'Help', at present the Scorables Global Message Handlers 'Help' takes control. I would like the FormFlows 'Help' Command when inside form and Scorables 'Help' only when its in other conversations. Is there a way to achieve this?
protected override async Task PostAsync(IActivity item, string state,
CancellationToken token)
{
var message = item as IMessageActivity;
if (message != null)
{
var helpDialog = new HelpDialog();
var interruption = helpDialog.Void<object, IMessageActivity>();
this.task.Call(interruption, null);
await this.task.PollAsync(token);
}
}
public class HelpDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
var userHelpMessage = "You have reached Help Section.\n"
+ StaticMessages.HelpMessage
+ "\n Your previous conversation is active
and you can return to prior dialog by typing 'Back' or 'Return'.";
await context.PostAsync(userHelpMessage);
context.Wait(this.MessageReceived);
}
private async Task MessageReceived(IDialogContext context,
IAwaitable<IMessageActivity> result)
{
var message = await result;
var incomingMessage = message.Text.ToLowerInvariant();
if ((incomingMessage != null) && (incomingMessage.Trim().Length > 0))
{
context.Done<object>(null);
}
else if (incomingMessage.Equals("return", StringComparison.InvariantCultureIgnoreCase))
{
context.Done<object>(null);
}
else if (incomingMessage.Equals("back", StringComparison.InvariantCultureIgnoreCase))
{
context.Done<object>(null);
}
else
{
context.Fail(new Exception("Message was not a string or was an empty string."));
}
}
}
[Serializable]
public class BuildARCollRaiseDisputeForm
{
//Custom Form Builder for custom yes options
BuildCustomForm customForm = new BuildCustomForm();
ARCollRaiseDisputeQuery disputeQuery = new ARCollRaiseDisputeQuery();
public IForm<ARCollRaiseDisputeQuery> BuildARCollRaiseDisputeForms()
{
IForm<ARCollRaiseDisputeQuery> form;
OnCompletionAsyncDelegate<ARCollRaiseDisputeQuery> processARCollRaiseDisputeSave = async (context, state) =>
{
var message = $"Saving your dispute reason against this Invoice. ";
await context.PostAsync(message);
};
var builder = customForm.CreateCustomForm<ARCollRaiseDisputeQuery>()
.Field(nameof(disputeQuery.BODISPUTEREASONOPTIONS),
validate: async(state, value) => await ValidatePymtDisputeReason(state, value)
)
.Field(nameof(disputeQuery.DisputeItemReasons),
active: state => ActiveDisputeItemReason(state)
)
.Field(nameof(disputeQuery.DisputeShipmentReasons),
active: ActiveDisputeShipmentReason
)
.Field(nameof(disputeQuery.DisputeInvoicingReasons),
active: ActiveDisputeInvoicingReason
)
.Field(new FieldReflector<ARCollRaiseDisputeQuery>(nameof(disputeQuery.OtherPymtDisputeReason))
.SetActive(state => ActiveOtherPymtDisputeReason(state))
.SetPrompt(new PromptAttribute("Do you have any custom message to my team for non-payment for this Invoice? If yes, then please type and send as single message."))
.SetValidate(async (state, value) => await ValidateOtherPymtDisputeReason(state, value))
)
.AddRemainingFields()
.Confirm("I have the following reasons for raising dispute against this Invoice and I am ready to submit your message. {BODISPUTEREASONOPTIONS} Shall I go ahead? ")
.OnCompletion(processARCollRaiseDisputeSave)
.Message("Thank you");
form = builder.Build();
return form;
}
}

ASP HttpClient GetAsync is not responding, nor timing out

I'm creating an Instagram API client on ASP MVC using HttpClient, I'm trying to make a get request but it fails without throwing exception or responding and doesn't respond to my timeout. Here is my code:
public class InstagramService
{
private HttpClient Client = new HttpClient {
BaseAddress = new Uri("https://api.instagram.com/v1/"),
Timeout = TimeSpan.FromMilliseconds(500)
};
public async Task<InstagramUser> GetInstagramUser(long? userId = null)
{
InstagramUser User = null;
string Parameter = (userId == null) ? "self" : userId.ToString();
try {
var response = await Client.GetAsync("users/" + Parameter + "/" + GetAccessToken());
if (response.IsSuccessStatusCode)
{
User = await response.Content.ReadAsAsync<InstagramUser>();
}
}catch(Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.InnerException.Message);
}
return User;
}
private string GetAccessToken()
{
return "?access_token=" + DB.config_det_sys.Single(i => i.codigo == "ACCESS_TOKEN_INSTAGRAM" && i.estado == true).Valor;
}
}
EDIT
Here I add how I call my service on the Home Controller, I will still test changing the controller to async Task
public class HomeController : Controller
{
private InstagramService IGService = new InstagramService();
public ActionResult About()
{
var apiCall = IGService.GetInstagramUser();
var model = apiCall.Result;
return View(model);
}
}
I tested on Postman trying to make the API call and it indeed worked, so where I'm failing to catch errors?
Your problem is here:
var model = apiCall.Result;
As I describe on my blog, you shouldn't block on asynchronous code. It can cause a deadlock.
Instead of Result, use await:
var model = await apiCall;
Adding to Stephen's answer, update the controller's action to be async all the way.
public class HomeController : Controller {
private InstagramService IGService = new InstagramService();
public async Task<ActionResult> About() {
var model = await IGService.GetInstagramUser();
return View(model);
}
}

Prevent deadlock by running a Task synchronously - Windows Service

I read that sometimes that calling directly a Task can lead to a deadlock of the main thread.
Here's my async method:
public async Task<List<JobsWithSchedules>> fillJobsAsync()
{
IOlapJobAccess jobAccess = new OlapJobAccess(_proxy, CentralPointPath);
List<OlapJob> jobs = await jobAccess.GetAllJobsAsync();
List<JobsWithSchedules> quartzJobs = null;
if (jobs != null)
{
quartzJobs = fillQuartzJobs(jobs);
}
return quartzJobs;
}
I tried a lot of ways to run this task in a sync function. Here's some examples:
public void syncFoo1()
{
var fillJobsTask = fillJobsAsync().ContinueWith((task) =>
{
if (task.Status == TaskStatus.RanToCompletion && task.Result != null)
{
List<JobsWithSchedules> quartzJobs = task.Result;
//...
}
else
{
//...
}
});
fillJobsTask.Wait();
}
public void syncFoo2()
{
Task.Run(() => fillJobsAsync()).ContinueWith((task) =>
{
if (task.Status == TaskStatus.RanToCompletion && task.Result != null)
{
List<JobsWithSchedules> quartzJobs = task.Result;
//...
}
else
{
//...
}
});
}
I want to know which is better solution to run the async method synchronously in the syncFoo() without causing deadlocks. Should I do it like in syncFoo2()?
PS: syncFoo() is called from a a windows service onStart() and onStop().
Since it's in a Windows service, the task should never run synchronously as Stephen suggested. I changed it to be async and it's working fine.

Scheduled task agent wp7 soap client does not work

I have this:
public class ScheduledAgent : ScheduledTaskAgent
{
...
protected override void OnInvoke(ScheduledTask task)
{
var userSettings = Utils.LoadSettings();
try
{
var client = new CheckinServiceSoapClient();
var token = WsApiCaller.Token;
var point = ... // User Location;
if (point != null)
{
client.UserTrackAsync(token, userSettings.UserId,
point.Latitude, point.Longitude,
point.Position.Location.HorizontalAccuracy,
point.Position.Location.Altitude,
point.Position.Location.Speed,
point.Position.Location.VerticalAccuracy,
point.Position.Location.Course,
"BACKGROUND_AGENT");
}
}
catch
{
}
NotifyComplete();
}
}
OnInvoke event occurs. But call of UserTrackAsync is not executing.
Your client.UserTrackAsync is an async call. The problem is that NotifyComplete(); is executed before client.UserTrackAsync has a chance to finish.
You need to call it in the UserTrackCompleted handler (and delete it from the original place):
client.UserTrackCompleted += (sender, args) =>
{
var res = args.Result.Retval;
NotifyComplete();
};

SignalR triggering events client-side in MVC3

I have an invoice importer hub like so:
public class ImporterHub : Hub, IDisconnect, IConnected
{
public void InvoiceImported(InvoiceImportedMessage message)
{
Clients["importer"].InvoiceImported(message);
}
public void FileImported(FileImportedMessage message)
{
Clients["importer"].FileImported(message);
}
public System.Threading.Tasks.Task Disconnect()
{
return Clients["importer"].leave(Context.ConnectionId, DateTime.Now.ToString());
}
public System.Threading.Tasks.Task Connect()
{
return Clients["importer"].joined(Context.ConnectionId, DateTime.Now.ToString());
}
public System.Threading.Tasks.Task Reconnect(IEnumerable<string> groups)
{
return Clients["importer"].rejoined(Context.ConnectionId, DateTime.Now.ToString());
}
}
In my controller, I'm capturing events for a long-running import process like so:
[HttpPost]
public ActionResult Index(IndexModel model)
{
if (ModelState.IsValid)
{
try
{
model.NotificationRecipient = model.NotificationRecipient.Replace(';', ',');
ImportConfiguration config = new ImportConfiguration()
{
BatchId = model.BatchId,
ReportRecipients = model.NotificationRecipient.Split(',').Select(c => c.Trim())
};
var context = GlobalHost.ConnectionManager.GetHubContext<ImporterHub>();
context.Groups.Add(this.Session.SessionID, "importer");
System.Threading.ThreadPool.QueueUserWorkItem(foo => LaunchFileImporter(config));
Log.InfoFormat("Queued the ImportProcessor to process invoices. Send Notification: {0} Email Recipient: {1}",
model.SendNotification, model.NotificationRecipient);
TempData["message"] = "The import processor job has been started.";
//return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
Log.Error("Failed to properly queue the invoice import job.", ex);
ModelState.AddModelError("", ex.Message);
}
}
private void LaunchFileImporter(ImportConfiguration config)
{
using (var processor = new ImportProcessor())
{
processor.OnFileProcessed += new InvoiceFileProcessing(InvoiceFileProcessingHandler);
processor.OnInvoiceProcessed += new InvoiceSubmitted(InvoiceSubmittedHandler);
processor.Execute(config);
}
}
private void InvoiceSubmittedHandler(object sender, InvoiceSubmittedEventArgs e)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ImporterHub>();
var message = new InvoiceImportedMessage()
{
FileName = e.FileName,
TotalErrorsInFileProcessed = e.TotalErrors,
TotalInvoicesInFileProcessed = e.TotalInvoices
};
context.Clients["importer"].InvoiceImported(message);
}
private void InvoiceCollectionSubmittedHandler(object sender, InvoiceCollectionSubmittedEventArgs e)
{
}
private void InvoiceFileProcessingHandler(object sender, InvoiceFileProcessingEventArgs e)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ImporterHub>();
var message = new FileImportedMessage()
{
FileName = e.FileName
};
context.Clients["importer"].FileImported(message);
}
I have the following script in my view for the importer:
<script type="text/javascript">
jQuery.connection.hub.logging = true;
var importerHub = jQuery.connection.importerHub;
importerHub.InvoiceImported = function (message) {
jQuery('#' + message.FileName + '_Invoices').text(message.TotalInvoicesInFileProcessed);
jQuery('#' + message.FileName + '_Errors').text(message.TotalErrorsInFileProcessed);
};
importerHub.FileImported = function (message) {
jQuery('#' + message.FileName + '_Processed').text('Done');
};
jQuery.connection.hub.start();
</script>
What I expected to happen:
I was expecting the server side events to trigger, which would send messages to the client,
which would, in turn, fire events to update the status of the import process.
What seems to be happening:
All server-side events trigger, all is well. The signalR library seems to initialize properly, but the events never fire, and I never get the updates to appear on the screen.
What am I doing wrong? This is my first attempt to use the signalR library, so it's entirely possible I'm doing everything wrong.
I believe your problem is that your client side hub events are named with init-caps and the default behavior of SignalR is to translate those to init-lower when publishing to the client to align with common JavaScript conventions. Try changing your hub event registrations to this:
importerHub.invoiceImported = function (message) {
AND
importerHub.fileImported = function (message) {

Categories

Resources