#f#
Вопрос:
Я прохожу через изменяемый раздел ConcurrentDictionary, чтобы удалить старые записи.
let private cache = ConcurrentDictionary<Instrument * DateTimeOffset, SmallSet>()
и поскольку я не могу удалять записи во время перебора ключей, мне было интересно, будет ли это хорошим использованием для Seq.cache:
let old = DateTimeOffset.UtcNow.AddHours(-1.)
cache.Keys
|> Seq.filter (fun x -> snd x <= old)
|> Seq.cache
|> Seq.iter (fun x -> cache.TryRemove x |> ignore)
Я никогда не использовал Seq.cache, и я предполагаю, что это создает разделение между двумя циклами. Правильно ли я понимаю, как это работает?
Комментарии:
1. fsharp.github.io/fsharp-core-docs/reference/…
2. У меня такое чувство, что этот код будет раздавлен
InvalidOperationException
. Лично я бы воспользовалсяcache.Keys |> Array.ofSeq |> Array.filter (...) |> Array.iter (...)
.Seq.cache
в любом случае будет выделяться место для каждого ключа, поэтому его лучше использоватьArray.ofSeq
, чтобы избежать создания временных буферов3. Последовательность кэша, возвращаемая, представляет
Seq.cache
собой оболочку вокруг базовой последовательности, в которую вы переходитеSeq.cache
. Оболочка гарантирует, что базовая последовательность повторяется ровно один раз, независимо от того, сколько раз вы повторяете кэш. Но он все еще ленив: он не начинает повторять базовую последовательность до тех пор, пока не начнет повторяться сам. Функция, которую вы хотите здесь, — этоSeq.toList
илиSeq.toArray
.
Ответ №1:
В описанном вами сценарии я не вижу причин, по которым вам нужно повторять коллекцию несколько раз. Вы можете просто просмотреть пары значений ключей внутри словаря и проанализировать каждую пару значений ключей, соответствует ли она вашему состоянию или нет. Итак, что-то вроде этого должно выполнить свою работу:
cache |> Seq.iter(function
| x when snd x.Key <= old -> cache.TryRemove(x.Key) |> ignore
| _ -> ())
Комментарии:
1. Чего я здесь не понимаю, так это как мы можем изменять коллекцию во время ее перебора? обычно вы не можете изменить то, что повторяете. Что отличает реализацию F# от других?
2. ConcurrentDictionary не является специфичным для F#, и это изменяемая коллекция. Этот трюк не сработал бы ни с одной неизменяемой коллекцией, такой как список F#. ConcurrentDictionary реализует IEnumerable, поэтому вы можете перебирать его с помощью Seq. Итер получает доступ к элементу, который является ключевым значением пары. А затем вы мутируете ConcurrentDictionary, удаляя ключ, соответствующий условию.
3. Если вы зацикливаетесь на изменяемом словаре в C#: foreach (var x в кэше)…, вы используете IEnumerable, и если вы измените коллекцию в течение этого времени, вы получите исключение. F# не выполняет итерацию одним и тем же способом?
4. В C# это работает точно так же, как и в F#, проверьте скрипку , чтобы убедиться в этом самостоятельно. Я думаю, что это не вызывает исключений из-за специальной реализации
GetEnumerator
, которая позволяет параллельное чтение и запись ConcurrentDictionary.5. ах, вы правы.. Я сохранил привычку не делать этого с неконкурентной коллекцией и на самом деле не думал, что это неприменимо к параллельной!