I unfortunately haven't found an existing question handling this weird constellation I'm dealing with:
I have an external SDK, which I cannot change. The communication is handled via external callbacks, that have to be implemented by interface:
interface class IMessageReceiver
{
void OnValueReceive(void* value, unsigned msgId);
}
Here's how I would request a value:
public ref class MyClass : public IMessageReceiver
{
public:
void OnValueReceive(void* value, unsigned msgId)
{
// ...
}
private:
void GetValue(string propertyName)
{
unsigned msgId = NewMsgID();
SDK.MsgGetValue(propertyName, msgId); // <- this will somewhere down the line call my OnValueReceive()
}
unsigned NewMsgID();
}
The way it works is, that I have to request a value by using GetValue() and simply wait for the other side (which is the SDK) to call my implemented OnValueReceive() once it decides to give me what I requested. I can identify my request by the unique msgId I pass as a second argument.
I have found examples of good implementations to wrap callback patterns into Tasks using TaskCompletionSource. But I have a hard time wrapping my head around translating it to my weird case.
I was thinking of maybe caching the TaskCompletionSources created in GetValue() in a dictionary of some sort, using the msgId as key. But that seems... dirty?
Also, this is C++/CLI.
You could use a Dictionary to keep track of any pending requests. I'm using c# for the example since my c++/cli is a bit rusty, but I hope it convey the general idea:
public class MyClass : IMessageReceiver
{
private ConcurrentDictionary<uint, TaskCompletionSource<object>> outstandingRequests = new();
Task<object> GetValue(string propertyName)
{
uint msgId = 0;// generate an ID somehow;
var tcs = new TaskCompletionSource<object>();
if (!outstandingRequests.TryAdd(msgId, tcs))
{
// error
}
// call API and return the task representing the work
SDK.MsgGetValue(propertyName, msgId);
return tcs.Task;
}
public void OnValueReceive(object value, uint msgId)
{
if (outstandingRequests.TryRemove(msgId, out var result))
{
result.SetResult(value);
}
else
{
// error
}
}
}
So you register task completion source objects in a dictionary. If only one concurrent request is allowed you could skip this. When a value is received you look up the corresponding task completion source and set its result to inform any caller that it is done.
Note that there are some potential questions/problems here. How is the API reporting any errors? Is there any support for cancellation or progress? Do you need some kind of timeout mechanism? If there is any situation where OnValueReceive will not be called you risk leaking objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With