等待一个已经 运行 的任务
Await an already running task
我的应用有一个像全局资源这样的概念。我的解决方案中的每个部分都需要包含对象集合。其中一些在启动时加载,其他在第一次访问时加载。
我的问题是第二种情况(延迟加载的集合)。下面是一个测试项目:
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";
}
}
我遇到的问题是 class GlobalResources
方法 LoadAsync
。在那里您可以找到代表我当前编码方式的注释“A”。这种方式是一个问题,因为如果发生第二个加载请求,那么我有两个数据库调用。
您可以在“B”部分找到我的解决方案。我想将第一个请求的第一个任务存储在一个字段中。如果有第二次调用,它应该等待存储的任务,以防止它第二次调用数据库。
我的问题是我不知道如何编码。如果我手动使用任务,我认为异步等待模式不起作用。或者你有什么想法吗?
首先,await
同一个 任务多次执行是可以的,它不会损害您的数据库。因此,至少,您可以缓存这样的任务(无论如何您都在锁定,所以它是安全的)并在以后重用它。顺便说一下,这可以让您最大限度地减少锁定时间。
其次,由于您的资源根据定义是全局的,因此预先计算长运行ning 查询似乎是合理的。这意味着您 运行 在构造函数中执行适当的任务(无需 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
根据评论,我可以更改 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);
}
}
我的应用有一个像全局资源这样的概念。我的解决方案中的每个部分都需要包含对象集合。其中一些在启动时加载,其他在第一次访问时加载。
我的问题是第二种情况(延迟加载的集合)。下面是一个测试项目:
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";
}
}
我遇到的问题是 class GlobalResources
方法 LoadAsync
。在那里您可以找到代表我当前编码方式的注释“A”。这种方式是一个问题,因为如果发生第二个加载请求,那么我有两个数据库调用。
您可以在“B”部分找到我的解决方案。我想将第一个请求的第一个任务存储在一个字段中。如果有第二次调用,它应该等待存储的任务,以防止它第二次调用数据库。
我的问题是我不知道如何编码。如果我手动使用任务,我认为异步等待模式不起作用。或者你有什么想法吗?
首先,await
同一个 任务多次执行是可以的,它不会损害您的数据库。因此,至少,您可以缓存这样的任务(无论如何您都在锁定,所以它是安全的)并在以后重用它。顺便说一下,这可以让您最大限度地减少锁定时间。
其次,由于您的资源根据定义是全局的,因此预先计算长运行ning 查询似乎是合理的。这意味着您 运行 在构造函数中执行适当的任务(无需 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
根据评论,我可以更改 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);
}
}