在非异步上下文中在后台线程上加载资源
Loading resource on background thread in non-async context
我正在使用一个外部库,它希望我在调用 GetImage
时为它公开的以下接口生成位图:
public interface IImageProvider
{
Bitmap GetImage(string imageId);
}
库批量请求它们 - 即它在 UI 线程上重复调用 GetImage()
,从而造成大量 UI 延迟。现在,我有时间在图书馆实际要求它们之前为这些 ID 中的每一个预渲染图像。我想在后台线程上这样做,但我显然无法通过界面返回 return Task<Bitmap>
。
我基本上想要实现的目标总结如下:假设我创建了一个库 - MySvgLibrary
:
public interface MySvgLibrary
{
void Preload();
Dictionary<string, Bitmap> Library { get; }
}
我现在想Task.Run(() => _myLibrary.Preload() }
。考虑到我不认为我可以在这里使用 async/await(因为我不能 return a Task<Bitmap>
,我不知道如何使用 TaskCompletionSource
在这种情况下。我怎么知道 Preload
完成了?我的意思是,我可以检查 Library
是否为 null
并旋转直到它不是(这确实有效,顺便说一句)但这种方法让我感到恶心。有什么建议吗?
这里是 MySvgLibrary
class 的一个实现。它使用 ConcurrentDictionary
for storing the bitmaps, and a SemaphoreSlim
来控制并行度(允许多少个线程并行创建图像)。
public class MySvgLibrary
{
private readonly ConcurrentDictionary<string, Task<Bitmap>> _dictionary;
private readonly SemaphoreSlim _semaphore;
public MySvgLibrary(int degreeOfParallelism = 1)
{
_dictionary = new ConcurrentDictionary<string, Task<Bitmap>>();
_semaphore = new SemaphoreSlim(degreeOfParallelism);
}
public Task<Bitmap> GetImageAsync(string key)
{
return _dictionary.GetOrAdd(key, _ => Task.Run(async () =>
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
return CreateImage(key);
}
finally
{
_semaphore.Release();
}
}));
}
public Bitmap GetImage(string key)
{
return GetImageAsync(key).GetAwaiter().GetResult();
}
public void PreloadImage(string key)
{
var fireAndForget = GetImageAsync(key);
}
private Bitmap CreateImage(string key)
{
Thread.Sleep(1000); // Simulate some heavy computation
return new Bitmap(1, 1);
}
}
用法示例:
var svgLibrary = new MySvgLibrary(degreeOfParallelism: 2);
svgLibrary.PreloadImage("SomeKey"); // the preloading happens in background threads
Bitmap bitmap = svgLibrary.GetImage("SomeKey"); // blocks if the bitmap is not ready yet
您应该将生成图像的实际代码放入 CreateImage
方法中。如果 CreateImage
抛出异常,将在调用 GetImage
时传播并重新抛出异常。
我正在使用一个外部库,它希望我在调用 GetImage
时为它公开的以下接口生成位图:
public interface IImageProvider
{
Bitmap GetImage(string imageId);
}
库批量请求它们 - 即它在 UI 线程上重复调用 GetImage()
,从而造成大量 UI 延迟。现在,我有时间在图书馆实际要求它们之前为这些 ID 中的每一个预渲染图像。我想在后台线程上这样做,但我显然无法通过界面返回 return Task<Bitmap>
。
我基本上想要实现的目标总结如下:假设我创建了一个库 - MySvgLibrary
:
public interface MySvgLibrary
{
void Preload();
Dictionary<string, Bitmap> Library { get; }
}
我现在想Task.Run(() => _myLibrary.Preload() }
。考虑到我不认为我可以在这里使用 async/await(因为我不能 return a Task<Bitmap>
,我不知道如何使用 TaskCompletionSource
在这种情况下。我怎么知道 Preload
完成了?我的意思是,我可以检查 Library
是否为 null
并旋转直到它不是(这确实有效,顺便说一句)但这种方法让我感到恶心。有什么建议吗?
这里是 MySvgLibrary
class 的一个实现。它使用 ConcurrentDictionary
for storing the bitmaps, and a SemaphoreSlim
来控制并行度(允许多少个线程并行创建图像)。
public class MySvgLibrary
{
private readonly ConcurrentDictionary<string, Task<Bitmap>> _dictionary;
private readonly SemaphoreSlim _semaphore;
public MySvgLibrary(int degreeOfParallelism = 1)
{
_dictionary = new ConcurrentDictionary<string, Task<Bitmap>>();
_semaphore = new SemaphoreSlim(degreeOfParallelism);
}
public Task<Bitmap> GetImageAsync(string key)
{
return _dictionary.GetOrAdd(key, _ => Task.Run(async () =>
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
return CreateImage(key);
}
finally
{
_semaphore.Release();
}
}));
}
public Bitmap GetImage(string key)
{
return GetImageAsync(key).GetAwaiter().GetResult();
}
public void PreloadImage(string key)
{
var fireAndForget = GetImageAsync(key);
}
private Bitmap CreateImage(string key)
{
Thread.Sleep(1000); // Simulate some heavy computation
return new Bitmap(1, 1);
}
}
用法示例:
var svgLibrary = new MySvgLibrary(degreeOfParallelism: 2);
svgLibrary.PreloadImage("SomeKey"); // the preloading happens in background threads
Bitmap bitmap = svgLibrary.GetImage("SomeKey"); // blocks if the bitmap is not ready yet
您应该将生成图像的实际代码放入 CreateImage
方法中。如果 CreateImage
抛出异常,将在调用 GetImage
时传播并重新抛出异常。