C
C#2y ago
D.Mentia

Turning an Event into a Task [Answered]

I have a method that needs to send a message over the network, and then wait for a response before returning something. Due to some quirks, the only way to get a response is via an event. What is an appropriate way to have the method wait for the event, and get the data from it?
I have tried setting up a TaskCompletionSource in a private field, that the method awaits, and the event sets the result - but the event could trigger from code I didn't write, and basically could end up returning the wrong data. And having to use a private field feels bad, though I don't know what alternatives I have
31 Replies
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
That example is good, yeah, thanks - though, I make a request that triggers incoming data, then need to wait for it. That is sort of what I have - but the problem is if say, another thread triggers some non-null incoming data after I trigger data, so that I get data from that other thread instead of mine
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
but it's possible that another message was just finishing when I start listening again, and I get that message instead of the one I want
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
but data would be null in this situation; data is set to null because we're waiting for a message. We get a message, we set data. But it wasn't the message we're waiting for I'm thinking, realistically, being threadsafe isn't possible unless I have something that can identify the message on both sides. And unless I use the full serialized content, I can't do that reliably because there's some code I can't touch
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
Or maybe something clever with semaphores, I'm writing up an example, it's work code so I can't show real code
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
and yeah I have to assume. Though realistically in my situation, it's for tests and I happen to know that I've overridden the extension method for all my tests so all of them come through here
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
so in theory it's OK as-is, but I'd like to make it threadsafe
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
I have to assume it is
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
It's also possible (and even likely) that the event is fired synchronously as part of the send, but that feels like a flimsy implementation detail to rely on
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
Right now my code looks most like this:
private ConcurrentDictionary<Guid,TaskCompletionSource> taskDictionary = new();
private SemaphoreSlim _lock = new SemaphoreSlim(1,1);
private Guid _currentMessageId;

public async Task DoThing(Message message) {
var tcs = new TaskCompletionSource();
_lock.Wait();
message.Send(); // External function, returns nothing, can't be modified
taskDictionary[_currentMessageId] = tcs;
_lock.Release();
}

return tcs.Task;
private ConcurrentDictionary<Guid,TaskCompletionSource> taskDictionary = new();
private SemaphoreSlim _lock = new SemaphoreSlim(1,1);
private Guid _currentMessageId;

public async Task DoThing(Message message) {
var tcs = new TaskCompletionSource();
_lock.Wait();
message.Send(); // External function, returns nothing, can't be modified
taskDictionary[_currentMessageId] = tcs;
_lock.Release();
}

return tcs.Task;
While in another method, when we get data about a messageId, we update CurrentMessageId
public void OnMessageSent(MessageData data)
{
_currentMessageId = data.GetMessageId();
}
public void OnMessageSent(MessageData data)
{
_currentMessageId = data.GetMessageId();
}
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
well, the thing I was pointing out with the code was that I don't have to wait for anything; when the Send is complete, my _currentMessageId is populated always. If it's synchronous, and in a lock, there is no chance that another message can trigger the event between them, right? Because most code would do, as you did, just invoke the event - synchronously. And the lock is still in effect while that's happening
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
Well. By synchronous I mean, it's a proper chain of awaits that should use only one thread max. But it's still async and awaiting. I'm not sure if that's really safe or just usually safe Anyway, thanks, you're right - unless there is some implementation detail that makes the event synchronous from the time of send, I have to have an ID or something. And yeah, I think ideally I'd specify the ID from the send Which would just require copying some ... pretty hefty code over from deep within the thing I don't want to modify, and bypassing most of the thing entirely. Which is not ideal, but... should work
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
Well basically I had a janky working version before, and was told that we'd need to get it approved and it would be shot down because it was janky. This is me trying to make it better Using serialized json content as a key in a dictionary worked better tho, I didn't have thread problems 😛
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
right, but first, setting the response ID is the hard part. But that'll just be trying to cut through lots of 'helper' code obscuring the things I need
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
The Network package doesn't let me do that. At least not at surface level
Unknown User
Unknown User2y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentia2y ago
As mentioned, I can grab some of its deep code and copy it to my code, and do it that way - but then I'm bypassing a lot of the package's code. So I'm unsure that's suitable, for a test, when the test may skip a lot of things But anyway. I'll figure it out from here, thanks
Accord
Accord2y ago
✅ This post has been marked as answered!
Want results from more Discord servers?
Add your server
More Posts