I found that Linq’s ZIP is really great for adjacent item computations.
For example, let’s have a collection of dates:
1: var dates = new DateTime[]
2: {
3: new DateTime(2000,1,1),
4: new DateTime(2000,1,2),
5: new DateTime(2000,1,5)
6: };
How would you compute time difference of adjacent items ?
I like to use Zip for this kind of job:
1: dates.Zip(dates.Skip(1), (d1, d2) => d2 - d1);
As you might expect, the result will be: { 1 day, 3 days }
Today I faced following problem:
User picks many files by OpenFileDialog control. They must be loaded asynchronously. Loading some of them may fail. I want to receive callback when loading completes. How can I encapsulate all of this in single repository Load method signature ?
After some time I came up with following interface:
1: interface IAsyncResult<out T>
2: {
3: T Result { get; }
4: Exception Error { get; }
5: bool IsCompleted { get; }
6: bool Wait(int timeout);
7: void AttachCallback(Action<IAsyncResult<T>> callback);
8: }
Similarity to .NET's IAsyncResult is intentional, although my version is generic and simplified.
Instead of supporting AsyncWaitHandle I expose only Wait method.
Example
Having IAsyncResult<T> we can define Image repository like this:
1: interface IImageRepository
2: {
3: IEnumerable<IAsyncResult<Image>> LoadImages();
4: }
Implementation
Following implementation wraps synchronous job to IAsyncResult<T> job.
ConcurrentBag was use to synchronize callbacks. Callbacks are stored as weak references to avoid memory leaks.
1: public class JobResult<T>
2: {
3: public T Result { get; set; }
4: public Exception Error { get; set; }
5: }
6:
7: public class AsyncResultFromSyncJob<T> : IAsyncResult<T>, IDisposable
8: {
9: private Func<JobResult<T>> synchronousJob;
10: private readonly ManualResetEvent completedEvent =
11: new ManualResetEvent(false);
12: private readonly ConcurrentBag<WeakReference> completionCallbacks =
13: new ConcurrentBag<WeakReference>();
14:
15: private void OnCompletion(IAsyncResult ar)
16: {
17: var jobResult = synchronousJob.EndInvoke(ar);
18: this.Result = jobResult.Result;
19: this.Error = jobResult.Error;
20: this.completedEvent.Set();
21:
22: synchronousJob = null;
23:
24: FireCallbacks();
25: }
26: private void FireCallbacks()
27: {
28: WeakReference wr;
29: Action<IAsyncResult<T>> callback;
30:
31: while (completionCallbacks.TryTake(out wr))
32: if ((callback = wr.Target as Action<IAsyncResult<T>>) != null)
33: Task.Factory.StartNew(() => callback(this));
34: }
35:
36: public AsyncResultFromSyncJob(Func<JobResult<T>> synchronousJob)
37: {
38: this.synchronousJob = synchronousJob;
39: synchronousJob.BeginInvoke(OnCompletion, null);
40: }
41:
42: #region IAsyncResult<T>
43:
44: public T Result { get; private set; }
45: public Exception Error { get; private set; }
46: public bool IsCompleted { get { return this.completedEvent.WaitOne(0); } }
47:
48: public bool Wait(int timeout = Timeout.Infinite)
49: {
50: return completedEvent.WaitOne(timeout);
51: }
52:
53: public void AttachCallback(Action<IAsyncResult<T>> callback)
54: {
55: completionCallbacks.Add(new WeakReference(callback));
56:
57: if(IsCompleted)
58: FireCallbacks();
59: }
60:
61: #endregion
62:
63: #region IDisposable
64:
65: public void Dispose()
66: {
67: completedEvent.Dispose();
68: }
69:
70: #endregion
71: }
I found Mark Seemann's IAsyncResult generic implementation after writing my own:
http://blogs.msdn.com/b/ploeh/archive/2007/02/09/agenericiasyncresultimplementation.aspx