Async / Await和ConfigureAwait - 稍后使用'unknown'方法签名执行任务

时间:2018-06-14 14:33:16

标签: c# asynchronous async-await c#-7.1

我一直在努力寻找解决这个问题的最佳方法。我一直在谷歌搜索几个小时,并且遇到了一些很好的资源(大多数都在这里),但我仍然无法弄清楚解决问题的最佳方法是什么。我发现的大多数提示要么没有提到ConfigureAwait(false),要么在以后存储任务执行时只有相同的方法签名。

我想要做的是首先确定在任务中运行什么方法。然后我想运行一些其他任务,然后确定任务。我编辑了下面的代码,试图让它更清晰。

在我的图书馆中,我有以下场景(大大简化):

t = new Task(() => OnClassB(c as ClassB)); 
// and then
t.Start();
await t.ConfigureAwait(false);

这一般都很好用。但是,OnClassA和OnClassB在OnAnyClass之前执行。当然,这是预期的行为,因为该方法立即执行并返回Task。但是,我希望OnAnyClass在其他之前执行。我已经google了很多关于如何处理这个问题的提示。我遇到了一些解决方案,但是我不确定哪种解决方案能够在我的方案中发挥最佳效果:

1。使用任务构造函数

t = new Task(async () => await OnClassB(c as ClassB).ConfigureAwait(false)); 
// and then
t.Start();
await t.ConfigureAwait(false);

然而,通过这种方式,我不确定代码将如何表现。我见过几个例子,但最后我才感到困惑。我是否仍然可以完全受益于ConfigureAwait(false)(这对我来说很重要,因为它是一个库代码),并且所有等待执行方法都会异步调用吗?

2。使用具有异步委托的任务构造函数

case 1:
    c = new ClassA(variable);
    await OnAnyClass(c).ConfigureAwait(false);
    await OnClassA(c as ClassA, "foo").ConfigureAwait(false);
    break;

现在,通过这种方法,我知道将正确使用ConfigureAwait(false)。但是,这会将Task包装在一个Task中 - 并且由于库的用户将能够覆盖这些方法,我认为它不是真的可取。

第3。使用Func< Task>

这种方法会很棒 - 但是,每种方法都有不同的签名,我想保持这种方式,所以如果不使代码比现在更麻烦,这对我来说真的不适用。

4。等待案例中的方法

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);

这种方法可以保证有效。但是,由于我的库的性质,可能有几十种情况 - 因此这会导致代码维护能力降低以及大量重复的代码行。

5。等待最后的所有方法

{{1}}

这也可以肯定,并且与第4号方法相比会减少重复行的数量,但是这段代码仍然非常烦人。

最糟糕的情况我可能会原样保留它,忽略顺序 - 在大多数情况下它应该不重要。但是,如果可能的话,我宁愿保留订单。哪种方法效果最好?由于它是库代码,我需要ConfigureAwait(false)才能正确执行。你有什么意见和建议?希望我以一种可以理解的方式解释我的问题。

提前感谢您,如果我遗漏了一些明显的东西,我表示歉意 - 我使用线程多年了,async / await对我来说相对较新。

2 个答案:

答案 0 :(得分:0)

试试这个:

    private List<Task> _tasks = new List<Task>();


    public async Task ProcessVariable(int variable)
    {
        BaseClass c = null;
        switch(variable)
        {
            case 1:
                c = new ClassA(variable);
                _tasks.Add(new Task(async () => await OnAnyClass(c)));
                _tasks.Add(new Task(async () => await OnClassA(c as ClassA, "foo")));
                break;
            case 2:
                //...
                throw new NotImplementedException();
        }
        foreach(var task in _tasks)
        {
            await task;
        }
    }

new Task()应该创建一个任务而不启动它。

答案 1 :(得分:0)

可能存在更可维护的路径,而不是增加圈复杂度,我们可以设置一组地图。这些地图的行为很像ifswitch,并且有可能以序列化形式存储以进行配置(并非总是如此),并且恕我直言,这是一个更清洁的过程。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class ClassFactory
    {
        private IDictionary<int, Func<int, BaseClass>> ClassMap { get; } = new Dictionary<int, Func<int, BaseClass>>()
        {
            {1, x => new ClassA(x) },
            {2, x => new ClassB(x) }
        };

        private IDictionary<Type, Func<BaseClass, Task>> ClassInitializeMap { get; } = new Dictionary<Type, Func<BaseClass,  Task>>()
        {
            {typeof(ClassA) , cls => Task.Delay(1000) }, //Do Something with "Foo" 
            {typeof(ClassB) , cls => Task.Delay(1000) } 
        };

        public async Task ProcessVariable(int variable)
        {
            var theClass = ClassMap[variable](variable);
            await OnAnyClass(theClass).ConfigureAwait(false);
            await ClassInitializeMap[theClass.GetType()](theClass).ConfigureAwait(false);
        }

        public Task OnAnyClass<T>(T anyClass) => Task.Delay(1000);
    }

    public abstract class BaseClass
    {
        public int Foo;
    }
    public class ClassA : BaseClass
    {
        public ClassA(int variable) => Foo = variable;
    }
    public class ClassB : BaseClass
    {
        public ClassB(int variable) => Bar = variable;
        public int Bar;
    }
}

我认为这可能是你的事情,这是未经测试的,所以如果看到任何疏忽或问题,请告诉我。

此外,要在ClassA / ClassB初始化方法中获取一组变量参数,您可以使用AspNet Core在注入IOptions时使用的类似模式。另一种选择是使用Dictionary<Type, Arg>来做“Bag Style”,其中Arg是一个基类,它带有每个类类型的参数。在这种情况下,Arg实际上只是字典的包装器,因为初始化方法将具有密钥的知识。您甚至可以从基础Arg派生到ClassAArgClassBArg。但是如果你想要一个例子,你可以处理细节或让我知道。