C# 可测试异步操作的合适模式?

C# 可测试异步操作的合适模式?,c#,unit-testing,design-patterns,asynchronous,C#,Unit Testing,Design Patterns,Asynchronous,我很难找到一个简单、灵活的模式来允许我在ViewModels中编写代码,这些代码在运行时异步运行,但在测试时同步运行。这就是我想到的——有人有什么建议吗?这条路走下去好吗?有更好的现有模式吗 LongRunningCall定义: public class LongRunningCall { public Action ExecuteAction { get; set; } public Action PostExecuteAction { get; set; } pub

我很难找到一个简单、灵活的模式来允许我在ViewModels中编写代码,这些代码在运行时异步运行,但在测试时同步运行。这就是我想到的——有人有什么建议吗?这条路走下去好吗?有更好的现有模式吗

LongRunningCall定义:

public class LongRunningCall
{
    public Action ExecuteAction { get; set; }
    public Action PostExecuteAction { get; set; }

    public LongRunningCall(Action executeAction = null, Action postExecuteAction = null)
    {
        ExecuteAction = executeAction;
        PostExecuteAction = postExecuteAction;
    }

    public void Execute(Action<Exception> onError)
    {
        try
        {
            ExecuteAction();
            PostExecuteAction();
        }
        catch (Exception ex)
        {
            if (onError == null)
                throw;
            onError(ex);
        }
    }

    public void ExecuteAsync(TaskScheduler scheduler, Action<Exception> onError)
    {
        var executeTask = Task.Factory.StartNew(ExecuteAction);
        var postExecuteTask = executeTask.ContinueWith((t) =>
            {
                if (t.Exception != null)
                    throw t.Exception;
                PostExecuteAction();
            }, scheduler);
        if (onError != null)
            postExecuteTask.ContinueWith((t) => { onError(t.Exception); });
    }
}
var continueCall = new LongRunningCall(continueCommand_Execute, continueCommand_PostExecute);
if (svc.IsAsyncRequired)
   continueCall.ExecuteAsync(TaskScheduler.FromCurrentSynchronizationContext(), continueCommand_Error);
else
   continueCall.Execute(continueCommand_Error);
唯一真正的先决条件是,您需要在运行时知道是否应该使用异步/同步。当我运行我的单元测试时,我发送一个模拟,告诉我的代码同步运行,当应用程序实际运行时,默认为true


反馈?

考虑更改
ExecuteAsync
,使其返回
任务

public Task ExecuteAsync(TaskScheduler scheduler, Action<Exception> onError)
但在单元测试中,我会等待任务实际完成:

var task = longRunningCall.ExecuteAsync(
    TaskScheduler.FromCurrentSynchronizationContext(),
    continueCommand_Error);
task.Wait();

我更愿意将关于是同步还是异步执行代码的决定封装在一个单独的类中,该类可以在如下接口后面进行抽象:

public interface ITaskExecuter
{
    void ScheduleTask(
        Action executeAction,
        Action postExecuteAction,
        Action<Exception> onException);
}
在调用类中没有用于测试和生产的单独代码路径

您可以选择编写以下测试:

  • 只需检查是否已将正确的操作传递给任务执行器,或者
  • 配置任务执行器以同步执行操作,并 测试所需结果,或
  • 两者都做

我在目前的工作中做了一些非常相似的事情,但现在无法使用代码进行复制/粘贴

基本上,我所做的是创建一个
IWorker
界面,使用
DoWork(Func)
方法

然后我创建了两个派生类,一个是“AsyncWorker”,一个是“SyncWorker”。SyncWorker只执行传入的
Func
(同步),而“AsyncWorker”是
BackgroundWorker
的包装器,它将传入的
Func
发送给BackgroundWorker进行异步处理

然后,我将ViewModel更改为传入一个
IWorker
。这会将依赖项解析移出ViewModel,因此可以使用Dep.Inj。实用工具(我使用Unity和构造函数注入)

因为我使用Unity,所以在我的单元测试配置中,我将
IWorker
映射到
SyncWorker
,并在生产中将
IWorker
映射到
AsyncWorker


希望这有意义。。。我知道如果我手头有代码会更容易…

这与我在回答中建议的解决方案相同。我将如何修改代码以选择性地获取参数?我是否必须使用ITaskExecutor和ScheduleTask复制ITaskExecutor及其实现(Func executeAction、Action postExecuteAction等…?有没有办法让我的TaskExecutor足够通用,可以像Task那样处理任何参数集,而不必做那么多定义?我原以为您会将execute Action和post executeAction作为匿名委托提供,其中已经包含任何参数,并且它们都是
Action
类型。例如
taskExecuter.ScheduleTask(()=>MyTask(1,2.3f,“随便”),()=>MyPostTask(true,false,“等等”),MyExceptionHandler(E));
public interface ITaskExecuter
{
    void ScheduleTask(
        Action executeAction,
        Action postExecuteAction,
        Action<Exception> onException);
}
taskExecuter.ScheduleTask(
    continueCommand_Execute,
    continueCommand_PostExecute,
    continueCommand_Error);