Skip to content

Concurrency scenarios and PLINQ

Rian Stockbower edited this page Nov 24, 2017 · 5 revisions

Ical.net does not have any parallelism built in. Instead, it's best for application developers to use the Task Parallel Library to achieve parallelism at the application level. Some examples are below.

Deserializing multiple calendars concurrently

public void GetOccurrencesConcurrently()
{
    var calendars = new List<string>
    {
        IcsFiles.DailyCount2,
        IcsFiles.DailyInterval2,
        IcsFiles.DailyByDay1,
        IcsFiles.RecurrenceDates1,
        IcsFiles.DailyByHourMinute1,
    };

    var eventOccurrences = calendars
        .AsParallel()
        .SelectMany(c =>
        {
            var calendar = Calendar.Load(c);
            // ...you could do something like computing some recurrences...
            var occurrences = calendar.GetOccurrences(searchStart, searchEnd);
            return occurrences;
        });

    var materialized = eventOccurrences.ToList();
}

Using parallelism to best effect

Where concurrency is used matters for application performance. In the example above, the events will be deserialized concurrently, but that's probably not the performance bottleneck. Calling GetOccurrences() is usually the most expensive part of a data processing pipeline, because evaluating RRULEs isn't cheap. You want to use your available CPU cores on each child CalendarEvent's GetOccurrences() invocation instead of at the top-level Calendar.

If I were going to use AsParallel() in a production scenario, I would rewrite the example above as follows:

public void GetOccurrencesConcurrently()
{
    var calendars = new List<string>
    {
        IcsFiles.DailyCount2,
        IcsFiles.DailyInterval2,
        IcsFiles.DailyByDay1,
        IcsFiles.RecurrenceDates1,
        IcsFiles.DailyByHourMinute1,
    };

    var eventOccurrences = calendars
        .Select(Calendar.Load)
        .SelectMany(=> c.Events)
        .AsParallel()
        .SelectMany(e => e.GetOccurrences(searchStart, searchEnd))
        .ToList();
}

Or if you want to get really fancy, you could use parallelism in both places, though it's probably overkill. Deserializing a typical calendar takes about 0.5ms on a mobile, 2012 Core i5, and it's likely that the task partitioning costs will wash out any gains. But you could try it:

public void GetOccurrencesConcurrently()
{
    var calendars = new List<string>
    {
        IcsFiles.DailyCount2,
        IcsFiles.DailyInterval2,
        IcsFiles.DailyByDay1,
        IcsFiles.RecurrenceDates1,
        IcsFiles.DailyByHourMinute1,
    };

    var eventOccurrences = calendars
        .AsParallel()
        .Select(Calendar.Load)
        .AsSequential()
        .SelectMany(=> c.Events)
        .AsParallel()
        .SelectMany(e => e.GetOccurrences(searchStart, searchEnd))
        .ToList();
}