I implemented already a polling-worker based on Timer. As example you can think of TryConnect
on the client side -- I call TryConnect
and it will connect eventually in some time. It handles multiple threads, if connecting is in the process already all subsequent TryConnect
returns immediately without any extra action. Internally I simply create a timer and in intervals I try to connect -- if the connection fails, I try again. And so on.
Small downside is it is "fire&forget" pattern and now I would like to combine it with "async/await" pattern, i.e. instead calling:
client.TryConnect(); // returns immediately
// cannot tell if I am connected at this point
I would like to call it like this:
await client.TryConnect();
// I am connected for sure
How can I change my implementation to support "async/await"? I was thinking about creating empty Task
(just for await
), and then complete it with FromResult
, but this method creates a new task, it does not complete given instance.
For the record, current implementation looks like this (just a sketch of the code):
public void TryConnect()
{
if (this.timer!=null)
{
this.timer = new Timer(_ => tryConnect(),null,-1,-1);
this.timer.Change(0,-1);
}
}
private void tryConnect()
{
if (/*connection failed*/)
this.timer.Change(interval,-1);
else
this.timer = null;
}
Lacking a good Minimal, Complete, and Verifiable code example it's impossible to offer any specific suggestions. Given what you've written, it's possible what you're looking for is TaskCompletionSource
. For example:
private TaskCompletionSource<bool> _tcs;
public async Task TryConnect()
{
if (/* no connection exists */)
{
if (_tcs == null)
{
this.timer = new Timer(_ => tryConnect(),null,-1,-1);
this.timer.Change(0,-1);
_tcs = new TaskCompletionSource<bool>();
}
await _tcs.Task;
}
}
private void tryConnect()
{
if (/*connection failed*/)
this.timer.Change(interval,-1);
else
{
_tcs.SetResult(true);
_tcs = null;
this.timer = null;
}
}
Notes:
TryConnect()
is called again after a connection is made. I expect what you really want is to also check for the presence of a valid connection, so I modified the above slightly to check for that. You can of course remove that part if you actually always want to attempt a new connection, even if one already exists._tcs
to null
immediate after setting its result. Note though that any code awaiting or otherwise having stored the Task
value for the _tcs
object will implicitly reference the current _tcs
object, so it's not a problem to discard the field's reference here.TaskCompletionSource
. So for scenarios where you just need a Task
, you can use the generic type with a placeholder type, like bool
as I've done here or object
or whatever. I could've called SetResult(false)
just as well as SetResult(true)
, and it wouldn't matter in this example. All that matters is that the Task
is completed, not what value is returned.async
keyword to make TryConnect()
an async method. IMHO this is a bit more readable, but of course does incur slight overhead in the extra Task
to represent the method's operation. If you prefer, you can do the same thing directly without the async
method:public Task TryConnect()
{
if (/* no connection exists */)
{
if (_tcs == null)
{
this.timer = new Timer(_ => tryConnect(),null,-1,-1);
this.timer.Change(0,-1);
_tcs = new TaskCompletionSource<bool>();
}
return _tcs.Task;
}
return Task.CompletedTask;
}