如何处理在一个平台上同步但在另一个平台上异步的代码

在编写基于Xamarin的跨平台C#应用程序时,我经常遇到的情况是在一个平台上同步但在另一个平台上异步的代码。 例如,文件访问在iOS,Android和Mac上是同步的,但在Windows上是异步的。

这导致了一些棘手的问题。 首先,在纯编码级别上,编译器希望方法是异步还是不同步。

但它比这更糟糕。 例如,如果iOS UI线程发现它需要加载一个小文件,并且在UI线程上同步这样做,这可能不会对用户造成任何明显的打嗝。 是的,我意识到,作为一般规则,人们希望预先加载这些资源。 但如果没有发生,并且文件很小,那就不会导致问题。

但是,如果从iOS UI代码调用C#异步方法并等待结果,应用程序将挂起。

结果是我们有两个方法,Foo()和FooAsync(),我们调用哪个方法取决于平台。 但我们不能只让整个事情变得异步。

鉴于此,如何组织一个代码尽可能地坚持DRY,同时在一个平台上保持同步并在另一个平台上保持同步?

**

编辑 – 响应一个例子的请求:这里有一些虚拟代码来说明问题。 如果有人打电话

await StringLoaders.ReadAllText(somePath) 

从iOS UI线程,结果是挂起。 是的,这个特定的例子有解决方法。 但一般问题是反复出现的问题。 如果应用程序有很多层,这是特别糟糕的,因为这样做 – 可能需要在整个代码中携带同步/异步区别,从而产生大量方法的两个版本。

 public class AsyncStringLoader { public async Task ReadAllTextAsync(string path) { return await Task.Run(() => "Hello"); } } public class StringLoader { public string ReadAllText(string path) { return "Hello"; } } public static class StringLoaders { public static object Current { get; set;} // already trouble here -- we can't really have a proper interface for ReadAllText. public static async Task ReadAllText(string path) { object loader = StringLoaders.Current; if (loader is StringLoader) { return ((StringLoader)loader).ReadAllText(path); } else if (loader is AsyncStringLoader) { return await ((AsyncStringLoader)loader).ReadAllTextAsync(path); // DRY violation, in addition to the hang } else { throw new Exception("Unknown loader!"); } } } 

如果你想要一个真实的例子,而不是一个愚蠢的例子,从NuGet下载PCLStorage库,然后尝试将它用于从iOS UI线程调用的任何东西。 。 。

您可以在顶层定义一个接口(或接口集),该接口仅包含异步方法以保持一致性。 此顶级接口将由更高级别的应用程序/ UI代码使用,因此更高级别的代码将该接口对象作为依赖项。 在该接口下面实现了每个操作系统类型的C#适配器,它们实现了异步接口方法。 对于Windows,您可以直接使用本机异步Windows API。 对于不提供异步api的其他操作系统,请使用其同步方法,但返回一个等待已计算结果的等待任务对象,如下所示:

 return Task.FromResult(/*the result of the sync api call from iOS*/); 

使用接口的更高级代码将等待接口的异步方法,而不用担心下面的实现。 如果底层实现是iOS,则调用将同步运行,就像您直接调用sync方法一样。 如果底层实现是Windows,则异步方法将运行。

我建议接口全部采用异步方法(vs所有同步)的原因是,使同步调用看起来像async(通过Task.FromResult)比强制异步调用同步工作更安全,这可能导致死锁。 另一个原因是,显然您希望尽可能多地利用async api,至少对于提供异步方法的操作系统。

您可以根据配置参数等确定接口的哪个实际具体实现(即Windows,IOS等)在运行时传递到更高级别的代码。您还可以使用现有的dependency injection容器之一用于向接口注册具体类。

您的问题有点开放,如果您正在寻找其他内容,请提供更多详细信息。