我的应用程序具有类似全球资源的概念。解决方案中的每个部分都需要包含一些对象集合。其中一些在启动时加载,其他在首次访问时加载。
我的问题是第二种情况(延迟加载的集合)。在下面,您可以看到一个测试项目:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Wellcome!");
var globalResources = new GlobalResources();
var toolClassA = new MyToolClass(globalResources);
var toolClassB = new MyToolClass(globalResources);
// This two "Init" calls can not be awaited in real project.
// They could be invoked by two different buttons
_ = toolClassA.InitAsync();
_ = toolClassB.InitAsync();
Console.WriteLine("Finish");
Console.ReadLine();
}
}
public class MyToolClass
{
private readonly GlobalResources _globalResources;
public MyToolClass(GlobalResources globalResources)
{
_globalResources = globalResources ?? throw new ArgumentNullException(
nameof(globalResources));
}
public async Task InitAsync()
{
await _globalResources.LoadAsync();
Console.WriteLine("awaited");
}
}
public class GlobalResources
{
public bool _isLoaded;
public readonly object _loadLock = new object();
private string _externalQueryResult;
private Task _runningLoadTask;
public async Task LoadAsync()
{
if (_isLoaded)
{
return;
}
lock (_loadLock)
{
if (_isLoaded)
{
return;
}
}
// A: Current code:
{
_externalQueryResult = await LongRunningQuery();
}
// B: Wanting something like this:
{
// If no task is already running then start one.
if (_runningLoadTask == null)
{
_runningLoadTask = LongRunningQuery();
await _runningLoadTask.Start(); // How await it?
}
// If a task ist already running then no db call should happen.
// Instead the currently running task should be awaited.
else
{
await Task.WaitAll(_runningLoadTask); // ???
return;
}
}
lock (_loadLock)
{
_isLoaded = true;
}
}
public async Task<string> LongRunningQuery()
{
// This method should only be called once.
Console.WriteLine("Loading data from Database (
this line should only be written once on console window).");
await Task.Delay(5000);
return "123";
}
}
我的问题是在课堂GlobalResources
方法LoadAsync
。在这里您可以找到注释“ A ”,它表示我当前的编码方式。那是个问题,因为如果第二个加载请求发生,那么我将有两个数据库调用。
您可以在“ B ”部分找到我的解决方案。我想将第一个请求的第一个任务存储在一个字段中。如果第二次调用进来,它应该等待存储的任务,以防止第二次调用数据库。
我的问题是我不知道如何编码。我认为如果手动使用“任务”,异步等待模式将无法正常工作。或您有任何想法吗?
解
在下面,您可以找到关键部分作为编译部分。(请注意damien-the-unbeliever的评论。发布的解决方案不是线程安全的!缺少一些锁)。
// B: Wanting something like this:
{
// If no task is already running then start one.
if (_runningLoadTask == null)
{
_runningLoadTask = LongRunningQuery();
await _runningLoadTask;
}
// If a task ist already running then no db call should happen.
// Instead the currently running task should be awaited.
else
{
await _runningLoadTask;
// Alternative way:
// await Task.WhenAll(_runningLoadTask);
}
}
首先,多次await
执行同一任务是可以的,并且不会损害您的数据库。因此,至少,您可以缓存此类任务(无论如何都可以锁定,这样很安全),以后再使用它即可。这样可以最大程度地减少锁定时间。
其次,由于您的资源在定义上是全局的,因此预先计算长时间运行的查询似乎是合理的。这意味着您可以在构造函数中运行适当的任务(不执行该任务await
),而无需锁定,根本不需要锁定。
public MyConstructor()
{
_myCachedTask = LongRunningTask(); // don't await it; hopefully, task is now running somewhere on the thread pool
}
...
public Task LongRunningTask() => _myCachedTask; // it's OK to await it multiple times, thread safe as well
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句