非线程安全的函数是否安全?

时间:2018-06-06 21:10:34

标签: c# asynchronous thread-safety

考虑以下用于修改非线程安全列表的异步函数:

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
}

4 个答案:

答案 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)

简短回答

你总是需要小心使用异步。

更长的答案

这取决于您的SynchronizationContextTaskScheduler,以及“安全”的含义。

当您的代码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一起使用的同步规则。

另见How do yield and await implement flow of control in .NET?