C# 如何在MVVM中组合异步和非异步服务? 脚本

C# 如何在MVVM中组合异步和非异步服务? 脚本,c#,mvvm,async-await,mvvm-light,C#,Mvvm,Async Await,Mvvm Light,在基于MVVMLight的应用程序中,我使用一些视图和服务来计算工作时间。除此之外,还有以下服务 数据服务:顾名思义,用于从数据库获取数据。因为本质上是I/O操作,所以这些方法使用的是async/await 合同:这是一个CPU绑定的服务,实际计算工作时间(例如,给定的一天、一周、一个月等) 现在,让我们来看看以下方法(位于合同服务中): 此方法位于合同服务中。它依赖于数据服务,以获取计算所需的数据 如您所见,DataService.GetWorkDay()目前是同步的。在相当长的一段时间内

在基于MVVMLight的应用程序中,我使用一些视图和服务来计算工作时间。除此之外,还有以下服务

  • 数据服务:顾名思义,用于从数据库获取数据。因为本质上是I/O操作,所以这些方法使用的是async/await
  • 合同:这是一个CPU绑定的服务,实际计算工作时间(例如,给定的一天、一周、一个月等)
现在,让我们来看看以下方法(位于合同服务中):

此方法位于
合同
服务中。它依赖于
数据服务
,以获取计算所需的数据

如您所见,
DataService.GetWorkDay()
目前是同步的。在相当长的一段时间内,数据库就是这样被访问的。现在,在将应用程序从WP8 Silverlight移植到UWP时,我想清理代码并更改数据服务,使其只提供
async
方法,而不是同步方法。但现在,我正在努力将两个实际上独立的服务组合起来,其中一个是同步的,而另一个是异步的

问题 我需要更新方法
getworktimeorday()
(以及许多其他类似的方法),以使用新的
DataService.getworkdaysync()
方法,该方法的名称如下所示:

public async Task<WorkDay> GetWorkDayAsync(Date dateKey)
虽然这看起来很简单,但它对代码的其余部分有很多影响。仅仅因为调用
getworktimeorday
的任何地方我都必须更改代码,因为这个方法现在是
async
。我必须等待它,因此可能还需要更改调用方法。。。等等但有时不需要异步调用
getworktimeorday()
,因为它已经在单独的线程或后台任务中运行。更不用说目前存在的所有单元测试了。因此,在某些情况下,它不会阻塞UI

此外,CPU限制的计算突然变得异步,这不是它应该如何工作的(至少如果我正确理解Stephen Cleary的post(和其他)的话)

2.分开服务 因此,我想到的下一个解决方案是分离服务。换句话说,不要让一个服务依赖于另一个服务。这将更改
getworktimeorday()
的签名,如下所示:

public TimeSpan GetWorkTimeForDay(WorkDay workDay, Date date)
{
    var finalWorkTime = TimeSpan.Zero;

    if (workDay != null)
        finalWorkTime = GetWorkTimeForDay(workDay);

    return finalWorkTime;
}
然后,调用方法将类似于以下内容:

public async void Caller(Date date)
{
    ...
    
    if (ContractCalculationCacheResults.Contains(date))
        return ContractCalculationCacheResults.Get(date);
    
    var workDay = await DataService.GetWorkDay(date);
    var workTime = Contract.GetWorkTimeForDay(workDay, date);

    ContractCalculationCacheResults.Put(date, workTime);

    ...
}
与其他解决方案一样,缺点是所有调用方方法都必须更新(及其调用方,…),尽管有时这是不必要的。此外,还将添加大量用于缓存和数据库访问的冗余代码

从MVVM的角度来看,另一个缺点可能是突然间数据被提供给了合同服务。因此,我不再使用DI,这或多或少是违反规则的,有充分的理由。(这里向我提出了如何正确地将服务注入服务的问题)

最后但并非最不重要的一点是,我还必须从
getworktimeorday
方法中删除缓存。只有当结果以前没有缓存时,我才会要求数据库获取工作日,然后再进行计算

3.

实际问题 在基于MVVM的项目中,将
async
服务与非
async
服务结合在一起的好设计是什么

我希望保持实际计算同步,因为它们是CPU限制的。我希望保持数据访问异步,因为它们不受CPU限制,但可能需要一些时间

因此,这篇文章不是关于如何等待
wait
/Task/…结果的技术解决方案,而是关于MVVM、注入到其他服务中的服务、同步和异步服务组合的架构问题

此外,CPU限制的计算突然变得异步,这不是它应该如何工作

这背后有一个简单的原因:当人们看到一个异步方法时,他们通常认为该方法将异步执行,因此不会阻塞当前线程

您的方法如下所示:

public async Task<TimeSpan> GetWorkTimeForDay(Date date)
{
    ...

    // Get the work day from the database in a synchronous fashion
    var workDay = await DataService.GetWorkDayAsync(date);

    ...
}
//Start...             (On current thread)
//Access the database  (On IO Thread, freeing up the current thread)
//Return to current thread
//Do the calculations  (On the current thread)
人们可能不期望的是调用线程上的工作负载

我会选择@kevin gosse:只需将其更改为异步方法,因为它仍然有一个IO绑定部分。如果您希望其他人调用该方法,请确保该文档也有一个CPU密集部分

斯蒂芬·克利里有一些很好的例子


您的第二种解决方案方法也是完全有效的。如果您认为您的“契约”纯粹是一个计算引擎,那绝对不应该关心它的数据来自哪里。您仍然有一个解耦的、可测试的类。

对我来说,它可能是一个重复的问题,而不是如何等待一个解决方案的答案
async
方法来完成。
此外,突然间CPU绑定的计算变得异步,这不是它应该如何工作的(至少如果我理解这篇文章(和其他文章)斯蒂芬·克利里(Stephen Cleary)的观点正确。
这只是一个一般性建议。如果您需要异步的方法,则不管它是IO还是CPU绑定。对于UI应用程序,您确实不应该进行任何冗长的计算(无论是IO还是CPU绑定)在UI线程上。对doPlus来说,使用异步方法是正确的,建议“不要仅仅因为CPU限制的操作而使方法异步”。这是因为计算将完全占用线程,因此使用当前线程比卸载到另一个线程更有效(假设这不是UI线程)。但是在您的示例中,您的方法是异步的,因为
G
//Start...             (On current thread)
//Access the database  (On IO Thread, freeing up the current thread)
//Return to current thread
//Do the calculations  (On the current thread)