I've been trying to find the best way for this issue. I've been googling for hours, and came across few good resources (most of them here), but I still can't figure out what is the BEST way to approach my issue. Most of the tips I found either don't mention ConfigureAwait(false), or simply have the same method signature when storing task execution for later.
What I want to do, is first determine what method to run in a task. Then I want to run some other task, and then determined task after that. I edited the code below to try to make it more clear.
In my library, I have a following scenario (greatly simplified):
public abstract class BaseClass
{
public int Foo;
}
public class ClassA : BaseClass { }
public class ClassB : BaseClass
{
public int Bar;
}
public async Task ProcessVariable(int variable)
{
BaseClass c = null; // initialized by child class
Task t = null;
switch (variable)
{
case 1:
c = new ClassA(variable); // parse data for ClassA and BaseClass
// potentially some special code here
t = OnClassA(c as ClassA, "foo");
break;
case 2:
c = new ClassB(variable); // parse data for ClassB and BaseClass
// potentially some special code here
t = OnClassB(c as ClassB);
break;
}
// first call different task than the one picked in switch
// can't call it before switch, as variable c is initialized in the switch body
if (c != null)
await OnAnyClass(c).ConfigureAwait(false);
// finally, call the async task we picked inside switch cases
if (t != null)
await t.ConfigureAwait(false);
}
public virtual async Task OnAnyClass(BaseClass c)
{
// this one does something
await SendAsync(c.Foo).ConfigureAwait(false);
}
public virtual async Task OnClassA(ClassA c, string additionalInfo)
{
// this one does nothing, but end user can override
await Task.CompletedTask;
}
public virtual async Task OnClassB(ClassB c)
{
// this one does something
await SendAsync(c.Bar).ConfigureAwait(false);
}
This works good in general. However, OnClassA and OnClassB get executed before OnAnyClass. Of course, this is expected behaviour, as the method gets executed instantly and returns Task. However, I'd like OnAnyClass to be executed before the other ones. I've googled a lot for a hint on how to approach this. I came across few solutions, however I am unsure which one is going to work the best with my scenario:
1. Use Task constructor
t = new Task(() => OnClassB(c as ClassB));
// and then
t.Start();
await t.ConfigureAwait(false);
However, with this way, I am unsure how the code will behave. I've seen few examples, but I got only confused in the end. Will I still get full benefit of ConfigureAwait(false) (which matters to me as it is a library code), and will all awaits in executed method be called properly asynchronously?
2. Use Task constructor with async delegate
t = new Task(async () => await OnClassB(c as ClassB).ConfigureAwait(false));
// and then
t.Start();
await t.ConfigureAwait(false);
Now, with this approach I know that ConfigureAwait(false) will be used properly. However, this will wrap Task inside a Task - and since users of library will be able to override the methods, I assume it's not really desirable.
3. Use Func<Task>
This approach would be great - however, each of the methods has different signature, and I want to keep it this way, so this won't really work for me without making code even more messy than it already is.
4. Await the methods within cases
case 1:
c = new ClassA(variable);
await OnAnyClass(c).ConfigureAwait(false);
await OnClassA(c as ClassA, "foo").ConfigureAwait(false);
break;
This approach is guaranteed to work. However, due to the nature of my library, there may be tens of cases - therefore this would lead to reduced code maintability and a lot of duplicated lines of code.
5. Await all the methods at the end
case 1:
c = new ClassA(variable);
break;
// and then
if (c != null)
await OnAnyClass(c).ConfigureAwait(false);
if (c is ClassA)
await OnClassA(c as ClassA, "foo").ConfigureAwait(false);
if (c is ClassB)
await OnClassB(c as ClassB).ConfigureAwait(false);
This also would work for sure, and would reduce amount of duplicate lines compared to approach number 4, however this code is still very annoying to maintain.
Worst case scenario I may leave it as it is, ignoring the order - in most cases it shouldn't matter much. However, if it's possible, I'd prefer to keep the order. Which of the approaches would work the best? Since it's a library code, I require ConfigureAwait(false) to perform correctly. What are you opinions and suggestions? Hopefully I explained my problem in an understandable way.
Thank you in advance, and my apologies if I am missing something obvious - I used threads for years, and async/await is relatively new for me.
There might be a more maintainable path, instead of increasing cyclomatic complexity we can setup a set of maps. Those maps act much like an
iforswitchwith the added benefit of the possibility to be stored in serialized form for configuration (not always though) and, IMHO, a much cleaner process.I think this might be what your after and this is untested so let me know if see any oversights or problems.
Further, to get a variable set of arguments into the
ClassA/ClassBinitialization methods you can use a similar pattern as AspNet Core uses when injectingIOptions. Another option is to do it "Bag Style" with aDictionary<Type, Arg>whereArgis a base class that carries the arguments for each class type. In that caseArgis really just a wrapper around a dictionary since the initialization methods will have knowledge of the keys. You can even go so far as to derive from the baseArgintoClassAArgandClassBArg. But you can work the details or let me know if you'd like an example.