Reputation: 793
[Disclaimer - this code is simplified (a lot) to easy reading and I know it doesent conform to normal code standards]
My problem can bee seen in the code below. Basically I have a caller that parses in an object. I have to wait until a subcomponent is finished - which is signaled by an event - before returning a value from that that is based on some value on the subcomponent.
The question is: What is the preferred pattern for situations like this (of course an actual solution would be most welcome).
I've experimented with different stuff around TaskCompletionSource etc. but I'm afraid my understanding is lagging to much to find an (preferably) elegant solution. Hope you can help.
public class AsyncEventTest
{
// This is performed one a single (UI) thread. The exception to this is
// a.B that might - at the calling time - get a asycronious update from the backend.
// The update is syncronized into the calling context so Task.Wait etc. will effectivly
// deadlock the flow.
public static string CallMe(A a)
{
if (a.B.State != State.Ready)
{
// wait for a.B.State == State.Ready ... but how ...
// await MagicMethod() ???;
}
// only execute this code after a.b.State == State.Ready
return a.B.Text;
}
}
public class A
{
public B B { get; set; }
}
public class B
{
public State State { get; private set; }
public event Action StateChanged;
public string Text { get; }
}
public enum State { Ready, Working, }
EDIT - example of what I tried I wonder if something like this is an acceptable approach (or if it even works)?
public class AsyncEventTest2
{
public static string CallMe(A a)
{
return CallMe1(a).Result;
}
public async static Task<string> CallMe1(A a)
{
await CallMe2(a);
return a.B.Text;
}
public static Task CallMe2(A a)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
if (a.B.State != State.Ready)
{
a.B.StateChanged += () =>
{
if (a.B.State == State.Ready)
tcs.SetResult(a.B.Text);
};
}
else
{
tcs.SetResult(a.B.Text);
}
return tcs.Task;
}
}
Upvotes: 4
Views: 1207
Reputation: 144126
You can register for the StateChanged
event and use a TaskCompletionSource
.
public static Task WaitForReady(this B b)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Action handler = null;
handler = () =>
{
if (b.State == State.Ready)
{
b.StateChanged -= handler;
tcs.SetResult(null);
}
};
b.StateChanged += handler;
return tcs.Task;
}
Beware there could be a race if the event could be raised before the handler is registered.
Upvotes: 6