我一直在努力寻找解决这个问题的最佳方法。我一直在谷歌搜索几个小时,并且遇到了一些很好的资源(大多数都在这里),但我仍然无法弄清楚解决问题的最佳方法是什么。我发现的大多数提示要么没有提到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对我来说相对较新。
答案 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)
可能存在更可维护的路径,而不是增加圈复杂度,我们可以设置一组地图。这些地图的行为很像if
或switch
,并且有可能以序列化形式存储以进行配置(并非总是如此),并且恕我直言,这是一个更清洁的过程。
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
派生到ClassAArg
和ClassBArg
。但是如果你想要一个例子,你可以处理细节或让我知道。