CommunityToolkit / CommunityToolkit/dotnet
[Feature] APIs to one-time subscribe to/await events
- Dominant language
- C#
- Stars
- 3.8k
- Forks
- 400
- PR merge metrics
- No merged PRs in 30d
Description
> Follow up from a request by @michael-hawker on his latest [Twitch stream](https://www.twitch.tv/videos/610404741?t=04h38m36s).
## Describe the problem this feature would solve
This issue is about having some APIs to allow users to subscribe to events just once, or to asynchronously await for an event to be raised, without having to manually define the handler, register/unregister the event from within the handler, and setup `Task`-s to await if needed.
_**Note:** that this issue is mostly just to gather feedbacks and discuss the idea at this point, I'm not actually opening a PR to add this feature. This is just to have a reference after the previous conversation._
## Describe the solution
I came up with a simple class that adds 3 methods that can be used to solve this issue. The signature can arguably seem a bit weird, but I don't think there's another way to do this at least at the moment, especially on UWP. Code generators might come in handy in the future, but those are still a ways off:
```csharp
public static class EventAwaiter
{
public static async Task Await(
Action register,
Action unregister,
THandler handler)
where THandler : Delegate;
public static async Task Await(
Action register,
Action unregister)
where THandler : Delegate;
public static async Task Await(
Action register,
Action unregister)
where THandler : Delegate
where TArgs : class;
}
```
The full code can be found in [my gist here](https://gist.github.com/Sergio0694/d91d59d230aca84dacd082ebb03ed63c).
The way these work is by creating a `TaskCompletionSource` behind the scenes, then using LINQ expression trees to wire up a custom handler that sets that task source, registering the handler, awaiting the task and then unregistering the handler. This is all completely transparent to the user, which just sees a nice API that supports the TPL workflow. Here's a sample usage:
```csharp
var button = new Button();
// BEFORE
TaskCompletionSource tcs = new TaskCompletionSource();
void Handler(object s, EventArgs e)
{
tcs.TrySetResult(null);
button.Loaded -= Handler;
}
button.Loaded += Handler;
await tcs.Task;
// AFTER
await EventAwaiter.Await(
h => button.Loaded += h,
h => button.Loaded -= h);
```
Those two lambdas to register/unregister events are needed right now as C# doesn't have proper support for registering events externally (this is by design, as far as I know).
## Describe alternatives you've considered
One possible alternative would be to leverage the APIs in the `System.Reactive` package, though that would require a whole new dependency just for this, and would still result in a very similar API surface for users anyway. Also that would probably involve even more overhead. With the proposed solution instead the whole thing is 100% self-contained and without external dependencies.
Contributor guide
Assessment
This issue has not been assessed yet.