创建异步服务
- 有些时候服务的实现里需要调用一些异步方法,比如异步访问网络(e.g. 使用HttpClient类)或数据库
- 这时需要将服务实现成异步的,例如下面的服务,包含的都是些异步方法
namespace BooksServiceSample.Services
{
public interface IBookChaptersService
{
Task AddAsync(BookChapter chapter);
Task AddRangeAsync(IEnumerable<BookChapter> chapters);
Task<BookChapter> RemoveAsync(Guid id);
Task<IEnumerable<BookChapter>> GetAllAsync();
Task<BookChapter> FindAsync(Guid id);
Task UpdateAsync(BookChapter chapter);
}
}
示例代码:
- 读写字典时,虽然此处定义为异步方法,但是实际上不需要异步,所以返回的Task使用FromResult方法创建,此有点“假异步”的意思,为一个简单实例
- 原本是void的,此时返回Task.CompletedTask
- 原本有返回值的,此时返回Task.FromResult(返回值)
namespace BooksServiceSample.Services
{
public class BookChaptersService : IBookChaptersService
{
private readonly ConcurrentDictionary<Guid, BookChapter> _chapters = new ConcurrentDictionary<Guid, BookChapter>();
public Task AddAsync(BookChapter chapter)
{
chapter.Id = Guid.NewGuid();
_chapters[chapter.Id] = chapter;
return Task.CompletedTask;
}
public Task AddRangeAsync(IEnumerable<BookChapter> chapters)
{
foreach (var chapter in chapters)
{
chapter.Id = Guid.NewGuid();
_chapters[chapter.Id] = chapter;
}
return Task.CompletedTask;
}
public Task<BookChapter> RemoveAsync(Guid id)
{
_chapters.TryRemove(id, out BookChapter removed);
return Task.FromResult(removed);
}
public Task<IEnumerable<BookChapter>> GetAllAsync() =>
Task.FromResult<IEnumerable<BookChapter>>(_chapters.Values);
public Task<BookChapter> FindAsync(Guid id)
{
_chapters.TryGetValue(id, out BookChapter chapter);
return Task.FromResult(chapter);
}
public Task UpdateAsync(BookChapter chapter)
{
_chapters[chapter.Id] = chapter;
return Task.CompletedTask;
}
}
}
- Controller只需要一点变化,就可以实现为异步版本
- Controller方法也返回一个Task, 这样就会很容易调用IBookChaptersService的异步方法
- 对于客户端来说,Controller实现为同步还是异步,并不重要。API的调用方式是相同的,客户端会为两种情形创建相同个Http请求
namespace BooksServiceSample.Controllers
{
[Produces("application/json", "application/xml")]
[Route("api/[controller]")]
public class BookChaptersController : Controller
{
private readonly IBookChaptersService _bookChaptersService;
public BookChaptersController(IBookChaptersService bookChaptersService)
{
_bookChaptersService = bookChaptersService;
}
[HttpGet()]
[ProducesResponseType(typeof(IEnumerable<BookChapter>), 200)]
public Task<IEnumerable<BookChapter>> GetBookChaptersAsync() =>
_bookChaptersService.GetAllAsync();
[HttpGet("{id}", Name = nameof(GetBookChapterByIdAsync))]
[ProducesResponseType(typeof(BookChapter), 200)]
[ProducesResponseType(404)]
public async Task<IActionResult> GetBookChapterByIdAsync(Guid id)
{
BookChapter chapter = await _bookChaptersService.FindAsync(id);
if (chapter == null)
{
return NotFound();
}
else
{
return new ObjectResult(chapter);
}
}
[HttpPost]
[ProducesResponseType(typeof(BookChapter), 201)]
[ProducesResponseType(400)]
public async Task<IActionResult> PostBookChapterAsync(
[FromBody]BookChapter chapter)
{
if (chapter == null)
{
return BadRequest();
}
await _bookChaptersService.AddAsync(chapter);
return CreatedAtRoute(nameof(GetBookChapterByIdAsync),
new { id = chapter.Id }, chapter);
}
[HttpPut("{id}")]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<IActionResult> PutBookChapterAsync(
Guid id, [FromBody]BookChapter chapter)
{
if (chapter == null || id != chapter.Id)
{
return BadRequest();
}
if (await _bookChaptersService.FindAsync(id) == null)
{
return NotFound();
}
await _bookChaptersService.UpdateAsync(chapter);
return new NoContentResult();
}
[HttpDelete("{id}")]
public async Task DeleteAsync(Guid id) =>
await _bookChaptersService.RemoveAsync(id);
}
}
- 如下这个IBookChaptersService的实现,不再是从字典里“假异步真同步”读写值,而是从数据库里“真异步”读写值
- 从数据库里读写值,使用了Entity Framework Core, 它支持异步读写值
namespace BooksServiceSample.Services
{
public class DBBookChaptersService : IBookChaptersService
{
private readonly BooksContext _booksContext;
public DBBookChaptersService(BooksContext booksContext)
{
_booksContext = booksContext;
}
public async Task AddAsync(BookChapter chapter)
{
await _booksContext.Chapters.AddAsync(chapter);
await _booksContext.SaveChangesAsync();
}
public async Task AddRangeAsync(IEnumerable<BookChapter> chapters)
{
await _booksContext.Chapters.AddRangeAsync(chapters);
await _booksContext.SaveChangesAsync();
}
public Task<BookChapter> FindAsync(Guid id) =>
_booksContext.Chapters.FindAsync(id);
public async Task<IEnumerable<BookChapter>> GetAllAsync() =>
await _booksContext.Chapters.ToListAsync();
public async Task<BookChapter> RemoveAsync(Guid id)
{
BookChapter chapter = await _booksContext.Chapters.SingleOrDefaultAsync(c => c.Id == id);
if (chapter == null) return null;
_booksContext.Chapters.Remove(chapter);
await _booksContext.SaveChangesAsync();
return chapter;
}
public async Task UpdateAsync(BookChapter chapter)
{
_booksContext.Chapters.Update(chapter);
await _booksContext.SaveChangesAsync();
}
}
}
namespace BooksServiceSample.Services
{
public class SampleChapters
{
private readonly IBookChaptersService _bookChaptersService;
public SampleChapters(IBookChaptersService bookChapterService)
{
_bookChaptersService = bookChapterService;
}
private string[] sampleTitles = new[]
{
".NET Applications and Tools",
"Core C#",
"Objects and Types",
"Object-Oriented Programming with C#",
"Generics",
"Operators and Casts",
"Arrays",
"Delegates, Lambdas, and Events"
};
private int[] numberPages = { 35, 42, 33, 20, 24, 38, 20, 32 };
public async Task CreateSampleChaptersAsync()
{
var chapters = new List<BookChapter>();
for (int i = 0; i < 8; i++)
{
chapters.Add(new BookChapter
{
Number = i,
Title = sampleTitles[i],
Pages = numberPages[i]
});
}
await _bookChaptersService.AddRangeAsync(chapters);
}
}
}
namespace BooksServiceSample.Models
{
public class BooksContext : DbContext
{
public BooksContext(DbContextOptions<BooksContext> options)
: base(options)
{
}
public DbSet<BookChapter> Chapters { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookChapter>()
.ToTable("Chapters")
.HasKey(c => c.Id);
modelBuilder.Entity<BookChapter>()
.Property(c => c.Id)
.HasColumnType("UniqueIdentifier")
.HasDefaultValueSql("newid()");
modelBuilder.Entity<BookChapter>()
.Property(c => c.Title)
.HasMaxLength(120);
}
}
}
namespace BooksServiceSample.Models
{
public class BookChapter
{
public Guid Id { get; set; }
[Required]
public int Number { get; set; }
[Required]
[MaxLength(40)]
public string Title { get; set; }
[DefaultValue(0)]
public int Pages { get; set; }
}
}