我使用LoadState方法作为示例,但这可以看作是异步编程的一般场景。
LoadState
和SaveState
方法实现通常具有以下签名:
public override void LoadState(..)
public async override void LoadState(..)
您可以选择添加async关键字,具体取决于您是否要等待加载某些数据。但由于LoadState
的返回类型为void
,因此无法等待LoadState方法本身,并将其作为fire和forget触发。这通常很好,因为它允许响应式UI,同时能够在数据加载逻辑中使用async / await。
在某些情况下,我想等待异步LoadState方法。但是,如果我将签名更改为Task
而不是void,那么所有不使用异步逻辑的实现都必须返回null(这似乎不是最佳解决方案)。是否有另一种可能的解决方案,其中可以等待一些方法调用,并且大多数都会停止并忘记(默认情况下)?
答案 0 :(得分:3)
async void
方法有几个问题:
await
完成它(如你所说)。async void
被称为“火与忘记”,但我自己也用过“火灾和崩溃”这个词。此外,您永远不应该从null
方法返回Async
。对于习惯TAP的其他程序员而言,这将是令人惊讶的。
您可以提供两种方法void LoadState
和Task LoadStateAsync
,但如果您这样做,我建议您始终提供这两种方法。 不是一个很好的简单方法来包装asynchronous methods in synchronous methods或synchronous methods in asynchronous methods,所以你实际上最终会得到两个几乎相同的实现。
出于维护原因,我并不特别喜欢这个。就个人而言,我更愿意让 - async
方法始终具有await
- 兼容签名:
interface IWhatever
{
Task LoadStateAsync();
}
如果实现是同步的,则实现应该是异步的或快速的。同步实现可以使用Task.FromResult
:
class Whatever : IWhatever
{
public Task LoadStateAsync()
{
... // fast-running synchronous code
return Task.FromResult<object>(null);
}
}
消费代码始终为async
:
Whatever whatever = ...;
await whatever.LoadStateAsync();
请注意,如果实现是同步的,则消费代码将不会屈服于其调用者;它将继续通过await
。
答案 1 :(得分:0)
我认为有一个Loaded事件在加载完成时会被触发。也许你可以在Loaded事件的事件处理程序中做任何想要继续的事情吗?
答案 2 :(得分:0)
您可以将您的逻辑实现为Task
- 返回方法,让LoadState
只丢弃返回值。
public override void LoadState() { LoadStateInner(); }
Task LoadStateInner() { await ...; }
现在,您可以在同时履行继承合同的同时调用这两种变体。
答案 3 :(得分:0)
我看到的一个可能的解决方案是在基类中定义2个方法(以及额外逻辑的第3个方法):
virtual void LoadState(){}
virtual async Task LoadStateAsync{ }
virtual void SomeOtherLogic(){}
您将调用LoadState,现在有2个方法调用:
LoadState();
await LoadStateAsync();
SomeOtherLogic();
在此解决方案中,如果SomeOtherLogic为空或不需要来自LoadState方法的数据,则可以覆盖LoadState方法。如果确实需要数据,则在执行SomeOtherLogic之前等待LoadStateAsync方法。
缺点是你(和其他开发者)总是要注意使用哪种方法。