我一直在努力找到解决这个问题的最佳方法。我已经在谷歌上搜索了几个小时,遇到了一些好的资源(其中大部分都在这里(,但我仍然无法弄清楚解决我的问题的最佳方法是什么。我发现的大多数技巧要么没有提到 ConfigureAwait(false(,要么在存储任务执行以供以后使用时使用相同的方法签名。
我想做的是,首先确定在任务中运行什么方法。然后我想运行一些其他任务,然后确定任务。我编辑了下面的代码以使其更清晰。
在我的库中,我有一个以下场景(大大简化(:
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);
}
这通常效果很好。但是,OnClassA 和 OnClassB 在 OnAnyClass 之前执行。当然,这是预期的行为,因为该方法会立即执行并返回 Task。但是,我希望OnAnyClass在其他类之前执行。我已经用谷歌搜索了很多关于如何处理这个问题的提示。我遇到了一些解决方案,但是我不确定哪一个最适合我的方案:
1. 使用任务构造函数
t = new Task(() => OnClassB(c as ClassB));
// and then
t.Start();
await t.ConfigureAwait(false);
但是,通过这种方式,我不确定代码的行为方式。我见过几个例子,但最后我只是感到困惑。我是否仍然会获得 ConfigureAwait(false( 的全部好处(这对我来说很重要,因为它是一个库代码(,并且执行方法中的所有等待都会被正确异步调用吗?
2. 将任务构造函数与异步委托一起使用
t = new Task(async () => await OnClassB(c as ClassB).ConfigureAwait(false));
// and then
t.Start();
await t.ConfigureAwait(false);
现在,通过这种方法,我知道ConfigureAwait(false(将被正确使用。但是,这会将 Task 包装在 Task 中 - 并且由于库的用户将能够覆盖这些方法,我认为这不是真正可取的。
3. 使用函数<任务>任务>
这种方法会很棒 - 但是,每个方法都有不同的签名,我想保持这种方式,所以如果不使代码比现在更混乱,这对我不起作用。
4. 等待案件中的方法
case 1:
c = new ClassA(variable);
await OnAnyClass(c).ConfigureAwait(false);
await OnClassA(c as ClassA, "foo").ConfigureAwait(false);
break;
这种方法保证有效。但是,由于我的库的性质,可能有数十种情况 - 因此这将导致代码可维护性降低和大量重复的代码行。
5.等待最后的所有方法
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);
这也肯定有效,并且与方法4相比,可以减少重复行的数量,但是维护此代码仍然非常烦人。
最坏的情况是,我可能会保持原样,忽略顺序 - 在大多数情况下,这应该无关紧要。但是,如果可能的话,我宁愿保留订单。哪种方法效果最好?由于它是一个库代码,我需要 ConfigureAwait(false( 才能正确执行。您有什么意见和建议?希望我以可以理解的方式解释我的问题。
提前感谢您,如果我错过了一些明显的东西,我深表歉意 - 我使用线程多年,而 async/await 对我来说相对较新。
试试这个:
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()
应在不启动任务的情况下创建任务。
可能有一条更易于维护的路径,我们可以设置一组地图,而不是增加圈的复杂性。这些映射的作用很像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>
"包样式",其中Arg
是一个基类,带有每个类类型的参数。在这种情况下Arg
实际上只是字典的包装器,因为初始化方法将知道键。您甚至可以从基础Arg
派生为ClassAArg
和ClassBArg
。但是您可以处理细节或让我知道您是否想要一个例子。