如何使用异步方法包装遗留同步 I/O 绑定操作?
How to wrap a legacy synchronous I/O bound operation with an asynchronous method?
我仍在学习 async/await
概念,请耐心等待。假设有这个遗留代码定义了从数据源(实际情况下是本地数据库)获取数据的合同:
public interface IRepository<T> {
IList<T> GetAll();
}
在新代码中,我有以下界面:
public interface IMyObjectRepository {
Task<IEnumerable<MyObject>> GetAllAsync();
}
MyObjectRepository
class 依赖于 IRepository<T>
是这样的:
public class MyObjectRepository : IMyObjectRepository
{
private readonly IRepository<Some.Other.Namespace.MyObject> repo_;
private readonly IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper_;
public BannerRepository(IRepository<Some.Other.Namespace.MyObject> repo, IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper)
{
repo_ = repo;
mapper_ = mapper;
}
}
如何以在异步编程上下文中有意义的方式实现 IMyObjectRepository.GetAllAsync()
方法?从一些本地数据源获取数据是一个 I/O 绑定操作,所以我这样做的方式是:
public async Task<IEnumerable<MyObject>> GetAllAsync()
{
var tcs = new TaskCompletionSource<IEnumerable<MyObject>>();
try
{
GetAll(objects =>
{
var result = new List<MyObject>();
foreach (var o in objects)
{
result.Add(mapper_.Map(o));
}
tcs.SetResult(result);
});
}
catch (Exception e)
{
tcs.SetException(e);
}
return await tcs.Task;
}
private void GetAll(Action<IEnumerable<Some.Other.Namespace.MyObject>> handle)
{
IEnumerable<Some.Other.Namespace.MyObject> objects = repo_.GetAll<Some.Other.Namespace.MyObject>();
if (objects is null)
{
objects = Enumerable.Empty<Some.Other.Namespace.MyObject>();
}
handle(objects);
}
这有意义吗?我不想使用 Task.Run()
,因为据我了解,在 I/O 绑定操作的情况下,这会白白浪费一个线程。
示例中的方法是有效的。然而,我会小心只在绝对需要时才使用它,因为用户在调用“异步”方法时可能会非常困惑,只是为了阻止它。从这个意义上讲,使用 Task.Run()
可能更可取,因为那样不会阻塞。但它确实要求调用的代码是线程安全的。
在您的示例中,使用 async
没有任何意义,只需删除它,await 应该会给出完全相同的结果,因为您无论如何都会返回一个任务。
使用 Task.FromResult
是另一种简单的选择。但如果出现异常,则不会完全相同。使用 TaskCompletionSource
和 .SetException
将使异常作为 Task.Exception 的 属性 而不是从方法调用本身引发。如果等待任务,则无关紧要,因为这两种情况都应由 try/catch 处理。但是还有其他方法可以处理任务的异常,因此您需要知道它是如何使用的,并提供适当的 documentation/comments 来描述行为。
不要用异步实现来实现同步方法。也不要实现伪异步方法(一种仅在线程池线程上运行同步代码的异步方法)。相反,先退后一步...
How do I implement the IMyObjectRepository.GetAllAsync() method in such a way that it makes sense in the context of asynchronous programming?
错误的问题。
不要通过说“我如何使这个异步”来接近异步,而是从另一端接近它。从 应该 异步的事情开始。如您所述,I/O 非常适合异步代码。因此,从实际 执行 I/O 的代码开始 - 最低级别的方法。这可能是 IRepository<T>.GetAll
- 实际与数据库进行通信的任何内容。将 that 更改为使用 await
调用异步等效项,并允许 async
从最低级别的方法扩展到代码的其余部分。
我仍在学习 async/await
概念,请耐心等待。假设有这个遗留代码定义了从数据源(实际情况下是本地数据库)获取数据的合同:
public interface IRepository<T> {
IList<T> GetAll();
}
在新代码中,我有以下界面:
public interface IMyObjectRepository {
Task<IEnumerable<MyObject>> GetAllAsync();
}
MyObjectRepository
class 依赖于 IRepository<T>
是这样的:
public class MyObjectRepository : IMyObjectRepository
{
private readonly IRepository<Some.Other.Namespace.MyObject> repo_;
private readonly IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper_;
public BannerRepository(IRepository<Some.Other.Namespace.MyObject> repo, IDataMapper<Some.Other.Namespace.MyObject, MyObject> mapper)
{
repo_ = repo;
mapper_ = mapper;
}
}
如何以在异步编程上下文中有意义的方式实现 IMyObjectRepository.GetAllAsync()
方法?从一些本地数据源获取数据是一个 I/O 绑定操作,所以我这样做的方式是:
public async Task<IEnumerable<MyObject>> GetAllAsync()
{
var tcs = new TaskCompletionSource<IEnumerable<MyObject>>();
try
{
GetAll(objects =>
{
var result = new List<MyObject>();
foreach (var o in objects)
{
result.Add(mapper_.Map(o));
}
tcs.SetResult(result);
});
}
catch (Exception e)
{
tcs.SetException(e);
}
return await tcs.Task;
}
private void GetAll(Action<IEnumerable<Some.Other.Namespace.MyObject>> handle)
{
IEnumerable<Some.Other.Namespace.MyObject> objects = repo_.GetAll<Some.Other.Namespace.MyObject>();
if (objects is null)
{
objects = Enumerable.Empty<Some.Other.Namespace.MyObject>();
}
handle(objects);
}
这有意义吗?我不想使用 Task.Run()
,因为据我了解,在 I/O 绑定操作的情况下,这会白白浪费一个线程。
示例中的方法是有效的。然而,我会小心只在绝对需要时才使用它,因为用户在调用“异步”方法时可能会非常困惑,只是为了阻止它。从这个意义上讲,使用 Task.Run()
可能更可取,因为那样不会阻塞。但它确实要求调用的代码是线程安全的。
在您的示例中,使用 async
没有任何意义,只需删除它,await 应该会给出完全相同的结果,因为您无论如何都会返回一个任务。
使用 Task.FromResult
是另一种简单的选择。但如果出现异常,则不会完全相同。使用 TaskCompletionSource
和 .SetException
将使异常作为 Task.Exception 的 属性 而不是从方法调用本身引发。如果等待任务,则无关紧要,因为这两种情况都应由 try/catch 处理。但是还有其他方法可以处理任务的异常,因此您需要知道它是如何使用的,并提供适当的 documentation/comments 来描述行为。
不要用异步实现来实现同步方法。也不要实现伪异步方法(一种仅在线程池线程上运行同步代码的异步方法)。相反,先退后一步...
How do I implement the IMyObjectRepository.GetAllAsync() method in such a way that it makes sense in the context of asynchronous programming?
错误的问题。
不要通过说“我如何使这个异步”来接近异步,而是从另一端接近它。从 应该 异步的事情开始。如您所述,I/O 非常适合异步代码。因此,从实际 执行 I/O 的代码开始 - 最低级别的方法。这可能是 IRepository<T>.GetAll
- 实际与数据库进行通信的任何内容。将 that 更改为使用 await
调用异步等效项,并允许 async
从最低级别的方法扩展到代码的其余部分。