является ли это хорошим использованием сек.кэша, в F#

#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. ах, вы правы.. Я сохранил привычку не делать этого с неконкурентной коллекцией и на самом деле не думал, что это неприменимо к параллельной!