I am trying to write a code that executes when a condition is met. Currently, I am using while...loop, which I know is not very efficient. I am also looking at AutoResetEvent() but i don't know how to implement it such that it keeps checking until the condition is true.
The code also happens to live inside an async method, so may be some kind of await could work?
private async void btnOk_Click(object sender, EventArgs e)
{
// Do some work
Task<string> task = Task.Run(() => GreatBigMethod());
string GreatBigMethod = await task;
// Wait until condition is false
while (!isExcelInteractive())
{
Console.WriteLine("Excel is busy");
}
// Do work
Console.WriteLine("YAY");
}
private bool isExcelInteractive()
{
try
{
Globals.ThisWorkbook.Application.Interactive = Globals.ThisWorkbook.Application.Interactive;
return true; // Excel is free
}
catch
{
return false; // Excel will throw an exception, meaning its busy
}
}
I need to find a way to keep checking isExcelInteractive() without CPU stuck in a loop.
Note: There is no event handler in Excel that would be raised when it is not in edit mode.
At least you can change your loop from a busy-wait to a slow poll. For example:
while (!isExcelInteractive())
{
Console.WriteLine("Excel is busy");
await Task.Delay(25);
}
Ended up writing this today and seems to be ok. Your usage could be:
await TaskEx.WaitUntil(isExcelInteractive);
code (including the inverse operation)
public static class TaskEx
{
/// <summary>
/// Blocks while condition is true or timeout occurs.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block.</param>
/// <param name="frequency">The frequency at which the condition will be check, in milliseconds.</param>
/// <param name="timeout">Timeout in milliseconds.</param>
/// <exception cref="TimeoutException"></exception>
/// <returns></returns>
public static async Task WaitWhile(Func<bool> condition, int frequency = 25, int timeout = -1)
{
var waitTask = Task.Run(async () =>
{
while (condition()) await Task.Delay(frequency);
});
if(waitTask != await Task.WhenAny(waitTask, Task.Delay(timeout)))
throw new TimeoutException();
}
/// <summary>
/// Blocks until condition is true or timeout occurs.
/// </summary>
/// <param name="condition">The break condition.</param>
/// <param name="frequency">The frequency at which the condition will be checked.</param>
/// <param name="timeout">The timeout in milliseconds.</param>
/// <returns></returns>
public static async Task WaitUntil(Func<bool> condition, int frequency = 25, int timeout = -1)
{
var waitTask = Task.Run(async () =>
{
while (!condition()) await Task.Delay(frequency);
});
if (waitTask != await Task.WhenAny(waitTask,
Task.Delay(timeout)))
throw new TimeoutException();
}
}
Example usage:
https://dotnetfiddle.net/Vy8GbV
You can use thread waiting handler
private readonly System.Threading.EventWaitHandle waitHandle = new System.Threading.AutoResetEvent(false);
private void btnOk_Click(object sender, EventArgs e)
{
// Do some work
Task<string> task = Task.Run(() => GreatBigMethod());
string GreatBigMethod = await task;
// Wait until condition is false
waitHandle.WaitOne();
Console.WriteLine("Excel is busy");
waitHandle.Reset();
// Do work
Console.WriteLine("YAY");
}
then some other job need to set your handler
void isExcelInteractive()
{
/// Do your check
waitHandle.Set()
}
Update:
If you want use this solution, you have to call isExcelInteractive() continuously with specific interval:
var actions = new []{isExcelInteractive, () => Thread.Sleep(25)};
foreach (var action in actions)
{
action();
}
This implementation is totally based on Sinaesthetic's, but adding CancellationToken and keeping the same execution thread and context; that is, delegating the use of Task.Run() up to the caller depending on whether condition needs to be evaluated in the same thread or not.
Also, notice that, if you don't really need to throw a TimeoutException and breaking the loop is enough, you might want to make use of cts.CancelAfter() or new CancellationTokenSource(millisecondsDelay) instead of using timeoutTask with Task.Delay plus Task.WhenAny.
public static class AsyncUtils
{
/// <summary>
/// Blocks while condition is true or task is canceled.
/// </summary>
/// <param name="ct">
/// Cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitWhileAsync(CancellationToken ct, Func<bool> condition, int pollDelay = 25)
{
try
{
while (condition())
{
await Task.Delay(pollDelay, ct).ConfigureAwait(true);
}
}
catch (TaskCanceledException)
{
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
// In this case, we only want to stop polling and finish this async Task.
}
}
/// <summary>
/// Blocks until condition is true or task is canceled.
/// </summary>
/// <param name="ct">
/// Cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitUntilAsync(CancellationToken ct, Func<bool> condition, int pollDelay = 25)
{
try
{
while (!condition())
{
await Task.Delay(pollDelay, ct).ConfigureAwait(true);
}
}
catch (TaskCanceledException)
{
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
// In this case, we only want to stop polling and finish this async Task.
}
}
/// <summary>
/// Blocks while condition is true or timeout occurs.
/// </summary>
/// <param name="ct">
/// The cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <param name="timeout">
/// Timeout in milliseconds.
/// </param>
/// <exception cref="TimeoutException">
/// Thrown after timeout milliseconds
/// </exception>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitWhileAsync(CancellationToken ct, Func<bool> condition, int pollDelay, int timeout)
{
if (ct.IsCancellationRequested)
{
return;
}
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
Task waitTask = WaitWhileAsync(cts.Token, condition, pollDelay);
Task timeoutTask = Task.Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask).ConfigureAwait(true);
if (!ct.IsCancellationRequested)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask.ConfigureAwait(true); // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
}
/// <summary>
/// Blocks until condition is true or timeout occurs.
/// </summary>
/// <param name="ct">
/// Cancellation token
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <param name="timeout">
/// Timeout in milliseconds.
/// </param>
/// <exception cref="TimeoutException">
/// Thrown after timeout milliseconds
/// </exception>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitUntilAsync(CancellationToken ct, Func<bool> condition, int pollDelay, int timeout)
{
if (ct.IsCancellationRequested)
{
return;
}
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
Task waitTask = WaitUntilAsync(cts.Token, condition, pollDelay);
Task timeoutTask = Task.Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask).ConfigureAwait(true);
if (!ct.IsCancellationRequested)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask.ConfigureAwait(true); // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
}
}
Try this
async void Function()
{
while (condition)
{
await Task.Delay(1);
}
}
This will make the program wait until the condition is not true.
You can just invert it by adding a "!" infront of the condition so that it will wait until the condition is true.
you can use SpinUntil which is buildin in the .net-framework. Please note: This method causes high cpu-workload.
After digging a lot of stuff, finally, I came up with a good solution that doesn't hang the CI :) Suit it to your needs!
public static Task WaitUntil<T>(T elem, Func<T, bool> predicate, int seconds = 10)
{
var tcs = new TaskCompletionSource<int>();
using(var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)))
{
cancellationTokenSource.Token.Register(() =>
{
tcs.SetException(
new TimeoutException($"Waiting predicate {predicate} for {elem.GetType()} timed out!"));
tcs.TrySetCanceled();
});
while(!cancellationTokenSource.IsCancellationRequested)
{
try
{
if (!predicate(elem))
{
continue;
}
}
catch(Exception e)
{
tcs.TrySetException(e);
}
tcs.SetResult(0);
break;
}
return tcs.Task;
}
}
You can use an async result and a delegate for this. If you read up on the documentation it should make it pretty clear what to do. I can write up some sample code if you like and attach it to this answer.
Action isExcelInteractive = IsExcelInteractive;
private async void btnOk_Click(object sender, EventArgs e)
{
IAsyncResult result = isExcelInteractive.BeginInvoke(ItIsDone, null);
result.AsyncWaitHandle.WaitOne();
Console.WriteLine("YAY");
}
static void IsExcelInteractive(){
while (something_is_false) // do your check here
{
if(something_is_true)
return true;
}
Thread.Sleep(1);
}
void ItIsDone(IAsyncResult result)
{
this.isExcelInteractive.EndInvoke(result);
}
Apologies if this code isn't 100% complete, I don't have Visual Studio on this computer, but hopefully it gets you where you need to get to.
Related
I am trying to start my own C# service as a Windows service. I installed my service using sc.exe create. I can see and modify the Service in the Services Application, but when I try to start, I am always getting the Error 1053.
In my Program I am starting a new Thread which runs an infinite while loop in Execute.
static void Main(string[] args) {
Console.WriteLine("Starting Thread");
mainThread = new Thread(new ThreadStart(Execute));
mainThread.Start();
Console.WriteLine("Thread Started!");
}
public static void Execute() {
//connect to Database
while(true) { //while connection open
...
}
}
When I run my Program manually, in a Console, Powershell and in Visual Studio, the Program runs as expected. When I try to run it via Services I get the error. I'm also getting the error when I'm running a empty Main (Main only with Console.WriteLine).
I googled really much and tried extending the Timeout in Registry(ServicesPipeTimeout), Using Threads, Installing Core Framework, Reinstalling the service and being owner of the service.
EDIT: I changed everything to run as a Hosted Service. In my Main i have
await new HostBuilder()
.ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); })
.Build()
.RunAsync();
But i Still can't run it as a Windows Service, I'm getting the same Error.
I am out of Ideas, any help is highly appreciated.
Regards
What you wrote is a standard Console app. A service shall expose Start and Stop facilities to be correctly interpreted by Windows (or by SystemD equivalently).
Your core loop of the main should be hosted on a IHostedService, or a Worker service.
Give a look at [here][1] to figure out what you have to do.
I give you here below a plausible Program.cs file that I wrote some time ago for a Linux service (there is actually no difference with Windows services, just remove the .UseSystemD() call).
/// <summary>
/// Remote service App for Monday Parser and Database manager.
/// </summary>
public static class Program
{
private static readonly ILogger logger = new LoggerConfiguration()
.ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog")
.CreateLogger().ForContext("Origin", "MondayService");
/// <summary>
/// Helper to create hosting environment.
/// </summary>
/// <param name="args">
/// command line arguments if any- no management is occurring now.
/// </param>
/// <returns>
/// </returns>
public static IHostBuilder CreateWebHostBuilder(string[] args)
{
string curDir = MondayConfiguration.DefineCurrentDir();
IConfigurationRoot config = new ConfigurationBuilder()
// .SetBasePath(Directory.GetCurrentDirectory())
.SetBasePath(curDir)
.AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true)
#if DEBUG
.AddJsonFile("appSettings.Debug.json")
#else
.AddJsonFile("appSettings.json")
#endif
.Build();
return Host.CreateDefaultBuilder(args)
.UseContentRoot(curDir)
.ConfigureAppConfiguration((_, configuration) =>
{
configuration
.AddIniFile("appSettings.ini", optional: true, reloadOnChange: true)
#if DEBUG
.AddJsonFile("appSettings.Debug.json")
#else
.AddJsonFile("appSettings.json")
#endif
.AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true);
})
.UseSerilog((_, services, configuration) => configuration
.ReadFrom.Configuration(config, sectionName: "AppLog")// (context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console())
// .UseSerilog(MondayConfiguration.Logger)
.ConfigureServices((hostContext, services) =>
{
services
.Configure<ServiceLocationOptions>(hostContext.Configuration.GetSection(key: nameof(ServiceLocationOptions)))
.Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(30));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
ServiceLocationOptions? locationOptions = config.GetSection(nameof(ServiceLocationOptions)).Get<ServiceLocationOptions>();
string url = locationOptions?.HttpBase + "*:" + locationOptions?.Port;
webBuilder.UseUrls(url);
})
.UseSystemd();
}
Here below you find the main implementation of the service, I added a lot of XML to show you all the exception you may get when moving things around in the service and which methods should be implmented as a minimum to have the service to work.
If you remove all the variables that you don't understand, you will remain with a working skeleton of a windows service running simply a ping service. Try it.
/// <summary>
/// Main Worker class for managing the service inside SystemD.
/// </summary>
public partial class MondayService : BackgroundService
{
/// <summary>
/// Initializes a new instance of the <see cref="MondayService"/> class. Std ctr.
/// </summary>
/// <param name="mondayhub">
/// </param>
/// <param name="containerServer">
/// </param>
public MondayService(IHubContext<MondayHub, IMondayServiceHub> mondayhub,
IFeatureContainer containerServer)
{
_containerServer = containerServer;
_dbManager = _containerServer.DbManager;
_parser = _containerServer.Parser;
_syslogQueue = _containerServer.SyslogQueue;
_segmentManager = _containerServer.SegmentManager;
_orderManager = _containerServer.OrderManager;
while (!MondayConfiguration.SerilogFactoryReady)
{
Thread.Sleep(20);
}
// _logger = MondayConfiguration.LoggerFactory.CreateLogger("");
_logger = new LoggerConfiguration().ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog").CreateLogger().ForContext("Origin", "MondayService");
_mondayHub = mondayhub;
}
/// <summary>
/// Setup activities for the Monday service.
/// </summary>
/// <param name="cancellationToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="OverflowException">
/// <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
/// or greater than <see cref="TimeSpan.MaxValue"/>.
/// -or- value is <see><cref>System.Double.PositiveInfinity</cref></see> .
/// -or- value is <see cref="double.NegativeInfinity"/>.
/// </exception>
/// <exception cref="AggregateException">
/// The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
/// contains a <see cref="TaskCanceledException"/> object.
/// -or- An exception was thrown during the execution of the task. The
/// <see><cref>InnerExceptions</cref></see> collection contains information about the
/// exception or exceptions.
/// </exception>
/// <exception cref="TaskCanceledException">
/// The task has been canceled.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref><name>function</name></paramref> parameter was <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="CancellationTokenSource"/> associated with <paramref
/// name="cancellationToken"/> was disposed.
/// </exception>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">
/// <paramref><name>sourceFileName</name></paramref> was not found.
/// </exception>
public override Task StartAsync(CancellationToken cancellationToken)
{
_logger.DebugInfo().Information("MondayService: Starting StartAsync");
var parserRenewed = false;
if (_parser is null)
{
_parser = new();
_parser.OnParserSocketDataArrived +=
(sender, e) => _containerServer.BroadcastParserSocketDataViaSignalR(sender, e);
parserRenewed = true;
Task.Run(ParserSubscriptions, cancellationToken);
_logger.DebugInfo().Information("MondayService: Instantiating again the parser inside StartAsync");
}
if (_dbManager is null || parserRenewed)
{
_dbManager = new(_parser);
_logger.DebugInfo().Information("MondayService: Instantiating again the db manager inside StartAsync");
_dbManager.ConnectToParserSocket();
_dbManager.OnFilteredSocketDataArrived +=
(sender, e) => _containerServer.BroadcastFilteredSocketDataViaSignalR(sender, e);
if (!_tagsDataSavedOnce)
{
// ReSharper disable once ExceptionNotDocumented
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
}
if (_segmentManager is null)
{
_segmentManager = new(_parser, _dbManager);
_segmentManager.ConnectToParserSocket(_parser);
_segmentManager.OnSegmentClosure += (sender, e) => _containerServer.BroadcastSegmentDataViaSignalR(sender, e);
_logger.DebugInfo().Information("MondayService: Instantiating again the segment manager inside StartAsync");
}
if (_orderManager is null)
{
_orderManager = new(_parser);
_orderManager.OnOrderManagerEvent +=
(sender, e) => _containerServer.BroadcastOrderManagerEventsViaSignalR(sender, e);
_logger.DebugInfo().Information("MondayService: Instantiating again the order manager inside StartAsync");
}
_logger.DebugInfo().Information("MondayService: Completing StartAsync");
return base.StartAsync(cancellationToken);
}
/// <summary>
/// Graceful shutdown and disposal of Monday service (parser and database manager comprised).
/// </summary>
/// <param name="cancellationToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="AggregateException">
/// The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
/// contains a <see cref="TaskCanceledException"/> object.
/// -or- An exception was thrown during the execution of the task. The
/// <see><cref>InnerExceptions</cref></see> collection contains information about the
/// exception or exceptions.
/// </exception>
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.DebugInfo().Information("MondayService: Starting StopAsync");
if (!_tagsDataSavedOnce)
{
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
_logger.DebugInfo().Information("Stopping Monday Service hosted on Linux.");
if (_parser is not null) await _parser.UnsubscribeAllAsync();
foreach (string ex in Tags.Exchanges.ExchangeNames.ToList())
{
_parser?.DeactivateRest(ex);
}
_parser?.Dispose();
_dbManager?.Dispose();
_orderManager?.Dispose();
_segmentManager?.Dispose();
_logger.DebugInfo().Information("MondayService: Completing StopAsync");
await base.StopAsync(cancellationToken);
}
/// <summary>
/// Core loop of the service. Here all the assets are instantiated and managed up to
/// final disposal. This instantiates the SignalR service and manages it.
/// </summary>
/// <param name="stoppingToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="TaskCanceledException">
/// The task has been canceled.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref><name>delay</name></paramref> represents a negative time interval other
/// than <see langword="TimeSpan.FromMilliseconds(-1)"/>.
/// -or- The <paramref><name>delay</name></paramref> argument's <see
/// cref="P:System.TimeSpan.TotalMilliseconds"/> property is greater than <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The provided <paramref><name>cancellationToken</name></paramref> has already been disposed.
/// </exception>
/// <exception cref="OverflowException">
/// <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
/// or greater than <see cref="TimeSpan.MaxValue"/>.
/// -or- <paramref><name>value</name></paramref> is <see cref="double.PositiveInfinity"/>.
/// -or- <paramref><name>value</name></paramref> is <see cref="double.NegativeInfinity"/>.
/// </exception>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">
/// <paramref><name>sourceFileName</name></paramref> was not found.
/// </exception>
/// <exception cref="DirectoryNotFoundException">
/// The specified path is invalid (for example, it is on an unmapped drive).
/// </exception>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.DebugInfo().Information("Monday Service starting at {Now}", DateTimeOffset.Now);
while (!stoppingToken.IsCancellationRequested)
{
Task tlog = FetchLogAsync(stoppingToken);
Task? tping = null;
int seconds = DateTimeOffset.Now.Second;
int minutes = DateTimeOffset.Now.Minute;
// logging a ping every 5 minutes
if (seconds < 5 && minutes % 1 == 0)
{
tping = Ping();
if (!_tagsDataSavedOnce)
{
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
}
// looping every 5 seconds
var tLoop = Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
if (tping is null)
{
await Task.WhenAll(tlog, tLoop);
}
else
{
await Task.WhenAll(tlog, tLoop, tping);
}
}
_logger.DebugInfo().Information("Monday Service stopping at {Now}", DateTimeOffset.Now);
}
[MustUseReturnValue]
private Task FetchLogAsync(CancellationToken stoppingToken)
{
var ok = true;
while (ok)
{
try
{
ok = _containerServer.SyslogQueue.Reader.TryRead(
out (LogLevel lvl, string line) item);
if (ok)
{
switch (item.lvl)
{
case LogLevel.Trace:
case LogLevel.Debug:
_logger.DebugInfo().Debug("{FetchedMessage}", item.line);
break;
case LogLevel.Information:
_logger.DebugInfo().Information("{FetchedMessage}", item.line);
break;
case LogLevel.Warning:
_logger.DebugInfo().Warning("{FetchedMessage}", item.line);
break;
case LogLevel.Error:
_logger.DebugInfo().Error("{FetchedMessage}", item.line);
break;
case LogLevel.Critical:
_logger.Fatal("{FetchedMessage}", item.line);
break;
case LogLevel.None:
break;
}
}
if (stoppingToken.IsCancellationRequested)
{
ok = false;
}
}
catch
{
ok = false;
}
}
return Task.CompletedTask;
}
private Task<CallResult<UpdateSubscription>> ParserSubscriptions()
{
Guard.Against.Null(nameof(_parser));
return _parser!.SubscribeFromSettingsAsync();
}
private async Task Ping()
{
await _containerServer.SyslogQueue.Writer.WriteAsync((
LogLevel.Information,
$"Monday Service active at: {DateTime.UtcNow.ToLocalTime()}"));
}
/// <summary>
/// This is a debug utility to check whether the service creates too many ThreaPpool threads.
/// </summary>
public static class ProcessTracker
{
static ProcessTracker()
{
}
/// <summary>
/// See https://stackoverflow.com/questions/31633541/clrmd-throws-exception-when-creating-runtime/31745689#31745689
/// </summary>
/// <returns>
/// </returns>
public static string Scan()
{
StringBuilder sb = new();
StringBuilder answer = new();
answer.Append("Active Threads").Append(Environment.NewLine);
// Create the data target. This tells us the versions of CLR loaded in the target process.
int countThread = 0;
var pid = Environment.ProcessId;
using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
{
// Note I just take the first version of CLR in the process. You can loop over
// every loaded CLR to handle the SxS case where both desktop CLR and .Net Core
// are loaded in the process.
ClrInfo version = dataTarget.ClrVersions[0];
var runtime = version.CreateRuntime();
// Walk each thread in the process.
foreach (ClrThread thread in runtime.Threads)
{
try
{
sb = new();
// The ClrRuntime.Threads will also report threads which have recently
// died, but their underlying data structures have not yet been cleaned
// up. This can potentially be useful in debugging (!threads displays
// this information with XXX displayed for their OS thread id). You
// cannot walk the stack of these threads though, so we skip them here.
if (!thread.IsAlive)
continue;
sb.Append("Thread ").AppendFormat("{0:X}", thread.OSThreadId).Append(':');
countThread++;
// Each thread tracks a "last thrown exception". This is the exception
// object which !threads prints. If that exception object is present, we
// will display some basic exception data here. Note that you can get
// the stack trace of the exception with ClrHeapException.StackTrace (we
// don't do that here).
ClrException? currException = thread.CurrentException;
if (currException is ClrException ex)
{
sb.Append("Exception: ")
.AppendFormat("{0:X}", ex.Address)
.Append(" (").Append(ex.Type.Name)
.Append("), HRESULT=")
.AppendFormat("{0:X}", ex.HResult)
.AppendLine();
}
// Walk the stack of the thread and print output similar to !ClrStack.
sb.AppendLine(" ------> Managed Call stack:");
var collection = thread.EnumerateStackTrace().ToList();
foreach (ClrStackFrame frame in collection)
{
// Note that CLRStackFrame currently only has three pieces of data:
// stack pointer, instruction pointer, and frame name (which comes
// from ToString). Future versions of this API will allow you to get
// the type/function/module of the method (instead of just the
// name). This is not yet implemented.
sb.Append(" ").Append(frame).AppendLine();
}
}
catch
{
//skip to the next
}
finally
{
answer.Append(sb);
}
}
}
answer.Append(Environment.NewLine).Append(" Total thread listed: ").Append(countThread);
return answer.ToString();
}
}
}
The main core loop is pinging a queue for logging. It is just a way to let something run over the service permanently while other classes do their work (in this case they are almost all based on EAP).
Definitely, look at MSDN at first. It seems that you are missing the basics.
[1]: https://learn.microsoft.com/en-us/dotnet/core/extensions/workers
I ended up writing it as a Windows Service. I simply inherited ServiceBase and wrote Start and Stop methods. The start method starts a Timer which calls a Method with the infinite loop every minute.
See: Error 1053: Service did not respond in time
I have created scan method using Scandit framework. Since I am using Xamarin.Forms there is no option to await the scan. Scandit prepared the callback as result of scanning. I would like to wrap that into one method which could be used with awaiter.
There is what I have done:
public async Task<Operation<string>> ScanAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
_codeScannedTcs = new TaskCompletionSource<Operation<string>>();
ScanditService.BarcodePicker.DidScan += OnScanFinished;
await ScanditService.BarcodePicker.StartScanningAsync();
var result = await Task.WhenAny(_codeScannedTcs.Task);
return result.Result;
}
catch (TaskCanceledException)
{
return Operation<string>.FailedOperation("A task was canceled");
}
catch (Exception ex)
{
return Operation<string>.FailedOperation(ex.Message);
}
finally
{
_codeScannedTcs = null;
ScanditService.BarcodePicker.DidScan -= OnScanFinished;
}
}
How in that situation I can use the cancellation token to break the scanning in any time?
I've used this code in the past:
/// <summary>
/// Allows the code waiting for an async operation to stop waiting when the cancellation token is set.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="task"></param>
/// <param name="cancellationToken"></param>
/// <see cref="https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/"/>
/// <returns></returns>
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(x => ((TaskCompletionSource<bool>)x).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
It is either verbatim copy pasted from a microsoft blog post or derivative of it
I have been here before, but this time I have managed to get further than I have before.
I have been following this guide and done everything it has said.
If I navigate to http://localhost:61589/help I actually see the help page, but there is only the introduction, there are no descriptions.
In my controller I have comments (always do) like this:
/// <summary>
/// For all answer related endpoints
/// </summary>
[RoutePrefix("answers")]
public class AnswersController : ApiController
{
// Readonly properties
private readonly IUnitOfWork _unitOfWork;
private readonly AnswerService _service;
private readonly StateService _stateService;
/// <summary>
/// Default constructor
/// </summary>
public AnswersController()
{
// Map our properties
this._unitOfWork = new UnitOfWork<DatabaseContext>();
this._service = new AnswerService(this._unitOfWork);
this._stateService = new StateService(this._unitOfWork);
}
/// <summary>
/// Get a list of answers
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetAllAsync()
{
try
{
// Return all our answers
return Ok(await this._service.GetAllAsync("States.Filters"));
// If there is an error
}
catch (Exception ex)
{
// Return our error
return BadRequest(ex.Message.ToString());
}
}
/// <summary>
/// Get a answer by id
/// </summary>
/// <param name="id">The answer id</param>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetAsync(int id)
{
try
{
// Return all our answers
return Ok(await this._service.GetAsync(id, "States.Filters"));
// If there is an error
}
catch (Exception ex)
{
// Return our error
return BadRequest(ex.Message.ToString());
}
}
/// <summary>
/// Create a answer
/// </summary>
/// <param name="model">The answer model</param>
/// <returns></returns>
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> CreateAsync(Answer model)
{
try
{
// Get our states
var all = await this._stateService.GetAllAsync("Filters");
var states = all.Where(m => model.States.Any(s => s.Id == m.Id)).ToList();
// Create our model
var answer = new Answer
{
Text = model.Text,
QuestionId = model.QuestionId,
Order = model.Order,
States = states
};
// Save our model
this._service.Create(answer);
// Save the database changes
await this._unitOfWork.SaveChangesAsync();
// Return our updated model
return Ok(answer);
// If there is an error
}
catch (Exception ex)
{
// Return our error
return BadRequest(ex.Message.ToString());
}
}
/// <summary>
/// Update a answer
/// </summary>
/// <param name="model">The answer model</param>
/// <returns></returns>
[HttpPut]
[Route("")]
public async Task<IHttpActionResult> UpdateAsync(Answer model)
{
try
{
// Create our model
var answer = new Answer
{
Id = model.Id,
QuestionId = model.QuestionId,
Order = model.Order,
Text = model.Text
};
// Save our model
this._service.Update(answer);
// Save the database changes
await this._unitOfWork.SaveChangesAsync();
// Return our updated model
return Ok(model);
// If there is an error
}
catch (Exception ex)
{
// Return our error
return BadRequest(ex.Message.ToString());
}
}
/// <summary>
/// Delete a answer
/// </summary>
/// <param name="id">The answer id</param>
/// <returns></returns>
[HttpDelete]
[Route("")]
public async Task<IHttpActionResult> DeleteAsync(int id)
{
try
{
// Get our model
var model = await this._service.GetAsync(id);
// Save our model
this._service.Remove(model);
// Save the database changes
await this._unitOfWork.SaveChangesAsync();
// Return Ok
return Ok();
// If there is an error
}
catch (Exception ex)
{
// Return our error
return BadRequest(ex.Message.ToString());
}
}
}
In the generated XmlDocument.xml I have these memebers:
<member name="T:Piiick.Api.Controllers.AnswersController">
<summary>
For all answer related endpoints
</summary>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.#ctor">
<summary>
Default constructor
</summary>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.GetAllAsync">
<summary>
Get a list of answers
</summary>
<returns></returns>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.GetAsync(System.Int32)">
<summary>
Get a answer by id
</summary>
<param name="id">The answer id</param>
<returns></returns>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.CreateAsync(Piiick.Data.Models.Answer)">
<summary>
Create a answer
</summary>
<param name="model">The answer model</param>
<returns></returns>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.UpdateAsync(Piiick.Data.Models.Answer)">
<summary>
Update a answer
</summary>
<param name="model">The answer model</param>
<returns></returns>
</member>
<member name="M:Piiick.Api.Controllers.AnswersController.DeleteAsync(System.Int32)">
<summary>
Delete a answer
</summary>
<param name="id">The answer id</param>
<returns></returns>
</member>
But they are not appearing on the actual help page.
Does anyone have any idea why this might be happening?
Turns out I only had to add this line:
GlobalConfiguration.Configure(WebApiConfig.Register);
to my Startup.cs file in the Configuration method.
Once this was added, it started working.
For anyone else, that looks like this:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// Get our http configuration
var config = new HttpConfiguration();
// Register all areas
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
// Use our web api
app.UseWebApi(config);
}
}
According to http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/creating-api-help-pages
You can use XML documentation comments to create the documentation. To enable this feature, open the file Areas/HelpPage/App_Start/HelpPageConfig.cs and uncomment the following line:
config.SetDocumentationProvider(new XmlDocumentationProvider(
HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
I'm building a Tic-Tac_toe api game. The game work ok, until one of the players press on a square that finish the game(either he wins or fill the board).
When I enabled all CLR exceptions for the last move, this is the exception I saw for line var user = await UserManager.FindByIdAsync(userId);
"Additional information: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."
Importent to know when a player click a square, it is a 'POST' method.
Here's my code:
public class GamesApiController : ApiController
{
ApplicationDbContext context = new ApplicationDbContext();
private ApplicationUserManager _userManager;
public IEnumerable<ApplicationUser> Get()
{
return context.Users;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? System.Web.HttpContext.Current.Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
#region Methods
/// <summary>
/// update the server data by reciving the model and square and returns the new model
/// </summary>
/// <param name="_model"></param>
/// <param name="squareId"></param>
/// <returns></returns>
//square clicked via post
[Route("api/gamesapi/{squareId}")]
public async Task<HttpResponseMessage> Post([FromBody]GameModel model, int squareId)
{
HttpResponseMessage response;
if (model == null)
{
//Utilites.CreateMsgCookie(Response, "Error", "Sorry, an unknown error has occurred");
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "model wasn't found");
return response;
}
//GameModel model = JsonConvert.DeserializeObject<GameModel>(_model);
Game game = GetGameById(model.Game.GameId);
if (game == null)
{
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "game wasn't found");
}
else
{
if (game.UserIdTurn == game.User1Id) //pressing user is user1
{
ChangeSquareState(game, squareId, true);
game.UserIdTurn = game.User2Id;
}
else //game.UserIdTurn == game.User2Id - pressing user is user2
{
ChangeSquareState(game, squareId, false);
game.UserIdTurn = game.User1Id;
}
SquareState[] board = new SquareState[] {game.Square1,game.Square2,game.Square3,game.Square4,
game.Square5,game.Square6,game.Square7,game.Square8,game.Square9};
if (didPlayerWin(board))
{
game.WinnerId = model.User.Id;
await UpdateUserGameState(1, game.User1Id);
await UpdateUserGameState(2, game.User2Id);
game.IsGameOver = true;
}
else
{
bool isBoardFull = true;
for (int i = 0; i < board.Length; i++)
{
if (board[i] == SquareState.Blank)
{
isBoardFull = false;
break;
}
}
if (isBoardFull)
{
await UpdateUserGameState(3, game.User1Id);
await UpdateUserGameState(3, game.User2Id);
game.IsGameOver = true;
}
}
context.SaveChanges();
response = Request.CreateResponse(HttpStatusCode.OK, game);
}
return response;
}
/// <summary>
/// When a game is over, recive a gameState and update the user. 1 for a win, 2 for loss, 3 for aa draw
/// </summary>
/// <param name="gameState"></param>
private async Task UpdateUserGameState(int gameState, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
switch (gameState)
{
case 1:
user.GamesWon++;
break;
case 2:
user.GamesLost++;
break;
case 3:
user.GamesDraw++;
break;
default:
break;
}
UserManager.UpdateAsync(user);
}
[HttpGet]
[Route("api/gamesapi/{gameId}")]
/// <summary>
/// method to bring the latest game's state from the context and send it back in a GameModel
/// </summary>
/// <param name="_model"></param>
/// <returns></returns>
public HttpResponseMessage Get(int gameId)
{
HttpResponseMessage response;
Game game = GetGameById(gameId);
if (game == null)
{
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "game wasn't found");
}
else
{
response = Request.CreateResponse(HttpStatusCode.OK, game);
}
return response;
}
/// <summary>
/// method that check if the board have a line(3 squars in a row)
/// of the same element of user1 or user2 , defult - returns fault
/// </summary>
/// <param name="board"></param>
/// <returns></returns>
private bool didPlayerWin(SquareState[] board)
{
}
/// <summary>
/// change the SquareState of a specific square of the sent game according to the pressing user
/// </summary>
/// <param name="game"></param>
/// <param name="SquareId"></param>
/// <param name="_isUser1"></param>
private void ChangeSquareState(Game game, int SquareId, bool _isUser1)
{
}
/// <summary>
/// get game from context by gameId , Defult result - null
/// </summary>
/// <param name="gameId"></param>
/// <returns></returns>
private Game GetGameById(int gameId)
{
}
#endregion
}
I think you missed to wait for UpdateUserAsync() in method UpdateUserGameState():
private async Task UpdateUserGameState(int gameState, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
// ...
// add missing await
await UserManager.UpdateAsync(user);
}
I'd like to await on a manual reset event with time-out and observing cancellation. I've come up with something like below. The manual reset event object is provided by an API beyond my control. Is there a way to make this happen without taking on and blocking a thread from ThreadPool?
static Task<bool> TaskFromWaitHandle(WaitHandle mre, int timeout, CancellationToken ct)
{
return Task.Run(() =>
{
bool s = WaitHandle.WaitAny(new WaitHandle[] { mre, ct.WaitHandle }, timeout) == 0;
ct.ThrowIfCancellationRequested();
return s;
}, ct);
}
// ...
if (await TaskFromWaitHandle(manualResetEvent, 1000, cts.Token))
{
// true if event was set
}
else
{
// false if timed out, exception if cancelled
}
[EDITED] Apparently, it makes sense to use RegisterWaitForSingleObject. I'll give it a try.
RegisterWaitForSingleObject will combine waits onto dedicated waiter threads, each of which can wait on multiple handles (specifically, 63 of them, which is MAXIMUM_WAIT_OBJECTS minus one for a "control" handle).
So you should be able to use something like this (warning: untested):
public static class WaitHandleExtensions
{
public static Task AsTask(this WaitHandle handle)
{
return AsTask(handle, Timeout.InfiniteTimeSpan);
}
public static Task AsTask(this WaitHandle handle, TimeSpan timeout)
{
var tcs = new TaskCompletionSource<object>();
var registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) =>
{
var localTcs = (TaskCompletionSource<object>)state;
if (timedOut)
localTcs.TrySetCanceled();
else
localTcs.TrySetResult(null);
}, tcs, timeout, executeOnlyOnce: true);
tcs.Task.ContinueWith((_, state) => ((RegisteredWaitHandle)state).Unregister(null), registration, TaskScheduler.Default);
return tcs.Task;
}
}
You also can use SemaphoreSlim.WaitAsync() which is similar to ManualResetEvent
Stephen's Cleary solution looks perfect. Microsoft provides the similar one.
As I haven't seen an example with cancellation logic.
Here it is:
public static class WaitHandleExtensions
{
public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken, int timeoutMilliseconds = Timeout.Infinite)
{
if (waitHandle == null)
throw new ArgumentNullException(nameof(waitHandle));
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
CancellationTokenRegistration ctr = cancellationToken.Register(() => tcs.TrySetCanceled());
TimeSpan timeout = timeoutMilliseconds > Timeout.Infinite ? TimeSpan.FromMilliseconds(timeoutMilliseconds) : Timeout.InfiniteTimeSpan;
RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle,
(_, timedOut) =>
{
if (timedOut)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(true);
}
},
null, timeout, true);
Task<bool> task = tcs.Task;
_ = task.ContinueWith(_ =>
{
rwh.Unregister(null);
return ctr.Unregister();
}, CancellationToken.None);
return task;
}
}
You can give this one a shot, https://www.badflyer.com/asyncmanualresetevent , tried to build upon the example on https://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx to support timeouts and cancellation.
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// An async manual reset event.
/// </summary>
public sealed class ManualResetEventAsync
{
// Inspiration from https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-1-asyncmanualresetevent/
// and the .net implementation of SemaphoreSlim
/// <summary>
/// The timeout in milliseconds to wait indefinitly.
/// </summary>
private const int WaitIndefinitly = -1;
/// <summary>
/// True to run synchronous continuations on the thread which invoked Set. False to run them in the threadpool.
/// </summary>
private readonly bool runSynchronousContinuationsOnSetThread = true;
/// <summary>
/// The current task completion source.
/// </summary>
private volatile TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>();
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
public ManualResetEventAsync(bool isSet)
: this(isSet: isSet, runSynchronousContinuationsOnSetThread: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
/// <param name="runSynchronousContinuationsOnSetThread">If you have synchronous continuations, they will run on the thread which invokes Set, unless you set this to false.</param>
public ManualResetEventAsync(bool isSet, bool runSynchronousContinuationsOnSetThread)
{
this.runSynchronousContinuationsOnSetThread = runSynchronousContinuationsOnSetThread;
if (isSet)
{
this.completionSource.TrySetResult(true);
}
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <returns>A task which completes when the event is set.</returns>
public Task WaitAsync()
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, default(CancellationToken));
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event.</returns>
public Task WaitAsync(CancellationToken token)
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken token)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, default(CancellationToken));
}
/// <summary>
/// Set the completion source.
/// </summary>
public void Set()
{
if (this.runSynchronousContinuationsOnSetThread)
{
this.completionSource.TrySetResult(true);
}
else
{
// Run synchronous completions in the thread pool.
Task.Run(() => this.completionSource.TrySetResult(true));
}
}
/// <summary>
/// Reset the manual reset event.
/// </summary>
public void Reset()
{
// Grab a reference to the current completion source.
var currentCompletionSource = this.completionSource;
// Check if there is nothing to be done, return.
if (!currentCompletionSource.Task.IsCompleted)
{
return;
}
// Otherwise, try to replace it with a new completion source (if it is the same as the reference we took before).
Interlocked.CompareExchange(ref this.completionSource, new TaskCompletionSource<bool>(), currentCompletionSource);
}
/// <summary>
/// Await completion based on a timeout and a cancellation token.
/// </summary>
/// <param name="timeoutMS">The timeout in milliseconds.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>A task (true if wait succeeded). (False on timeout).</returns>
private async Task<bool> AwaitCompletion(int timeoutMS, CancellationToken token)
{
// Validate arguments.
if (timeoutMS < -1 || timeoutMS > int.MaxValue)
{
throw new ArgumentException("The timeout must be either -1ms (indefinitely) or a positive ms value <= int.MaxValue");
}
CancellationTokenSource timeoutToken = null;
// If the token cannot be cancelled, then we dont need to create any sort of linked token source.
if (false == token.CanBeCanceled)
{
// If the wait is indefinite, then we don't need to create a second task at all to wait on, just wait for set.
if (timeoutMS == -1)
{
return await this.completionSource.Task;
}
timeoutToken = new CancellationTokenSource();
}
else
{
// A token source which will get canceled either when we cancel it, or when the linked token source is canceled.
timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(token);
}
using (timeoutToken)
{
// Create a task to account for our timeout. The continuation just eats the task cancelled exception, but makes sure to observe it.
Task delayTask = Task.Delay(timeoutMS, timeoutToken.Token).ContinueWith((result) => { var e = result.Exception; }, TaskContinuationOptions.ExecuteSynchronously);
var resultingTask = await Task.WhenAny(this.completionSource.Task, delayTask).ConfigureAwait(false);
// The actual task finished, not the timeout, so we can cancel our cancellation token and return true.
if (resultingTask != delayTask)
{
// Cancel the timeout token to cancel the delay if it is still going.
timeoutToken.Cancel();
return true;
}
// Otherwise, the delay task finished. So throw if it finished because it was canceled.
token.ThrowIfCancellationRequested();
return false;
}
}
}
Alternative solution: wait for the handles of the task and the manual reset event
I was having memory leaks when using Task.WaitAny() with a Task (returned by SqlConnection.OpenAsync()') and a Manual Reset Event received as parameter and wrapped in a Task with AsTask(). These object were not being disposed: TaskCompletionSource<Object>, Task<Object>, StandardTaskContinuation, RegisteredWaitHandle, RegisteredWaithandleSafe, ContinuationResultTaskFromresultTask<Object,bool>, _ThreadPoolWaitOrTimerCallback).
This is real production code, used in a Windows service, of a function that tries to open a connection to a db in a loop until the connection is opened, or the operation fails, or the ManualResetEvent _finishRequest, received as parameter in the function containing this code, is signaled by code in any other thread.
To avoid the leak, I decided to do it the other way round: wait for the handles of the _finishRequest and the Task returned by OpenAsync():
Task asyncOpening = sqlConnection.OpenAsync();
// Wait for the async open to finish, or until _finishRequest is signaled
var waitHandles = new WaitHandle[]
{
// index 0 in array: extract the AsyncWaitHandle from the Task
((IAsyncResult)asyncOpening).AsyncWaitHandle,
// index 1:
_finishRequest
};
// Check if finish was requested (index of signaled handle in the array = 1)
int whichFinished = WaitHandle.WaitAny(waitHandles);
finishRequested = whichFinished == 1;
// If so, break the loop to exit immediately
if (finishRequested)
break;
// If not, check if OpenAsync finished with error (it's a Task)
if (asyncOpening.IsFaulted)
{
// Extract the exception from the task, and throw it
// NOTE: adapt it to your case. In mine, I'm interested in the inner exception,
// but you can check the exception itself, for example to see if it was a timeout,
// if you specified it in the call to the async function that returns the Task
var ex = asyncOpening?.Exception?.InnerExceptions?[0];
if (ex != null) throw ex;
}
else
{
Log.Verbose("Connection to database {Database} on server {Server}", database, server);
break;
}
If you also need the timeout, you can include it in the call to OpenAsync, or you asyn function, and then check if the result of the async operation was cancelled because of the timeout: check the status of the Task when finished, as you can see in the NOTE in the code comment.