Reputation: 8446
I am exposing a WaitHandle
on an interface for an object that's responsible for creating a resource internally, but it may take a long time. The contract looks like this:
public interface IHaveExpensiveResources {
WaitHandle FinishedInitialization { get; }
}
The WaitHandle
starts unset, but then I set it once the initialization completes (in the background, after the constructor returns). The thing I don't like about this is that consumers of this contract can set the WaitHandle
themselves, when they have no business being able to do this. It reminds me of the difference between a get-only IList<T>
property and a IReadOnlyList<T>
.
Is there anything in C# like a WaitOnlyWaitHandle
which only exposes the WaitOne()
overloaded methods?
So that I'm not a victim of the XY problem, is there a more canonical way of asynchronously constructing objects which have long construction times?
Upvotes: 2
Views: 126
Reputation: 8446
For completeness, this is the interface and implementation that I created for exposing on the WaitOne()
overloaded methods on WaitHandle
public interface IWaitOnlyWaitHandle
{
bool WaitOne();
bool WaitOne(TimeSpan timeout);
bool WaitOne(int millisecondsTimeout);
bool WaitOne(int millisecondsTimeout, bool exitContext);
bool WaitOne(TimeSpan timeout, bool exitContext);
}
public sealed class WaitOnlyWaitHandle : IWaitOnlyWaitHandle
{
# UPDATE: this object does not own the passed in WaitHandle
private readonly WaitHandle _waitHandle;
public WaitOnlyWaitHandle(WaitHandle waitHandle)
{
_waitHandle = waitHandle;
}
public bool WaitOne() => _waitHandle.WaitOne();
public bool WaitOne(TimeSpan timeout) => _waitHandle.WaitOne(timeout);
public bool WaitOne(int millisecondsTimeout) => _waitHandle.WaitOne(millisecondsTimeout);
public bool WaitOne(int millisecondsTimeout, bool exitContext) => _waitHandle.WaitOne(millisecondsTimeout, exitContext);
public bool WaitOne(TimeSpan timeout, bool exitContext) => _waitHandle.WaitOne(timeout, exitContext);
}
UPDATE: as per @stevenbone's comment, this implementation does not own the lifecycle for the WaitHandle
that is passed in to the constructor. Read his comments for more info.
Upvotes: 3
Reputation: 127603
Do you have to pass back a WaitHandle
or could you make your own wrapper class that only exposes the functions you want, similar to what ReadOnlyCollection does?
One option is to wrap the WaitHandle up in to a task (Code example taken from the MSDN)
public static Task WaitOneAsync(this WaitHandle waitHandle)
{
if (waitHandle == null)
throw new ArgumentNullException("waitHandle");
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle,
delegate { tcs.TrySetResult(true); }, null, -1, true);
var t = tcs.Task;
t.ContinueWith( (antecedent) => rwh.Unregister(null));
return t;
}
Then you just call .WaitOneAsync()
on whatever FinishedInitialization
would have been returning and return that Task
instead.
Upvotes: 4