考虑以下用于修改非线程安全列表的异步函数:
async Task AddNewToList(List<Item> list)
{
// Suppose load takes a few seconds
Item item = await LoadNextItem();
list.Add(item);
}
简单地说:这样安全吗?
我担心的是,可以调用异步方法,然后在加载时(在另一个线程上,或作为I / O操作),调用者可以修改列表。
假设调用者正在执行list.Clear()的过程中,突然Load方法完成了!会发生什么?
任务会立即中断并运行list.Add(item);
代码吗?或者它会等到主线程完成所有计划的CPU任务(即:等待Clear()完成),然后再运行代码?
编辑:由于我基本上已经为自己解答了这个问题,所以这是一个额外的问题:为什么?为什么它立即中断而不是等待CPU绑定操作完成?不排队自己似乎是违反直觉的,这是完全安全的。
编辑:这是我自己测试的另一个例子。评论表明执行的顺序。我很失望!
TaskCompletionSource<bool> source;
private async void buttonPrime_click(object sender, EventArgs e)
{
source = new TaskCompletionSource<bool>(); // 1
await source.Task; // 2
source = null; // 4
}
private void buttonEnd_click(object sender, EventArgs e)
{
source.SetResult(true); // 3
MessageBox.Show(source.ToString()); // 5 and exception is thrown
}
答案 0 :(得分:2)
不,它不安全。但是也要考虑调用者也可能在调用代码之前生成一个线程并将List传递给它的子线程,即使在非异步环境中也会产生相同的不利影响。
因此;虽然不安全,但无论如何都没有关于从调用者接收List的固有线程安全性 - 无法知道该列表是否实际上是从您自己的其他线程处理的。
答案 1 :(得分:0)
假设&#34;无效的访问&#34;在LoadNextItem()
中发生:Task
将抛出异常。由于捕获了上下文,它将传递给调用者线程,因此将无法访问list.Add
。
所以,不,它不是线程安全的。
答案 2 :(得分:0)
是的,我认为这可能是一个问题。
我会返回项目并添加到主踏板上的列表中。
private async void GetIntButton(object sender, RoutedEventArgs e)
{
List<int> Ints = new List<int>();
Ints.Add(await GetInt());
}
private async Task<int> GetInt()
{
await Task.Delay(100);
return 1;
}
但你必须从async和async打电话,所以我不这样做也可以。
答案 3 :(得分:0)
你总是需要小心使用异步。
这取决于您的SynchronizationContext和TaskScheduler,以及“安全”的含义。
当您的代码awaits
出现问题时,它会创建一个延续并将其包装在一个任务中,然后将该任务发布到当前的SynchronizationContext的TaskScheduler中。然后,上下文将确定延续的运行时间和位置。默认调度程序只使用线程池,但不同类型的应用程序可以扩展调度程序并提供更复杂的同步逻辑。
如果您正在编写一个没有SynchronizationContext的应用程序(例如,一个控制台应用程序或anything in .NET core),那么继续只是放在线程池上,并且可以与您的主线程并行执行。在这种情况下,您必须使用lock
或同步对象(例如ConcurrentDictionary<>
而不是Dictionary<>
),除了本地引用或closed之外的任何其他任务。
如果您正在编写WinForms应用程序,则会将延迟放入消息队列中,并且所有这些都将在主线程上执行。这样可以安全地使用非同步对象。但是,还有其他担忧,例如deadlocks。当然,如果您生成任何线程,则必须确保它们使用lock
或并发对象,以及any UI invocations must be marshaled back to the UI thread。另外,如果你有足够的疯狂来写一个带有多个消息泵的WinForms应用程序(这是非常不寻常的),你需要担心同步任何常见变量。
如果您正在编写ASP.NET应用程序,SynchronizationContext将确保对于给定的请求,没有两个线程同时执行。您的延续可能在不同的线程上运行(由于称为thread agility的性能特性),但它们将始终具有相同的SynchronizationContext,并且保证没有两个线程将同时访问您的变量(假设,当然,它们不是静态的,在这种情况下它们跨越HTTP请求并且必须同步)。此外,管道将阻止对同一会话的并行请求,以便它们串行执行,因此您的会话状态也受到保护,不受线程问题的影响。但是你仍然需要担心死锁。
当然,您可以write your own SynchronizationContext并将其分配给您的主题,这意味着您指定了将与async
一起使用的同步规则。