Reputation: 18567
I have a WCF service (IMyService) that I wrap into a service (ICentralService), so that I have one central service that I can inject in my ViewModels. This would give me the advantage of changing/adding things at one location just before the WCF service is called.
Now because I need to make an async wcf call, my viewmodel also needs to be async. I'm have a callback from my viewmodel, but my CentralService also has its own callback to call the End... method.
Question: what is the best way to pass my viewmodel-callback to the EndTest method in the central service, so that this EndTest method could notify the callback on the viewmodel?
Or maybe there is a better way? I could directly inject IMyService in my ViewModel but then I don't have a central location (CentralService) where I can manipulate/inject data before sending it to the server via WCF.
Note: I'm on .NET 4.0, can't use "await", I'm also using WCF IAsyncResult Model (server-side).
Code:
[ServiceContract(....)]
public interface IMyService {
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginTest(int x, AsyncCallback, object state);
int EndTest(IAsyncResult result);
}
public interface ICentralService {
void WorkItAsync(int x, AsyncCallback callback);
}
public class CentralService : ICentralService
{
private IMyService _Srv;
public CentralService(IMyService srv)
{
_Srv = srv;
}
public void WorkItAsync(int x, AsyncCallback callback)
{
// callback is the callback from my viewmodel
_Srv.BeginTest(x, new AsyncCallback(WorkItCompleted));
}
private void WorkItCompleted(IAsyncResult r)
{
// ...
int result = _Srv.EndTest(r);
// Need to call callback from viewmodel now to notify it is ready.
}
}
public class SomeViewModel : INotifyPropertyChanged
{
private ICentralService _Central;
public SomeViewModel(ICentralService central) {
_Central = central;
}
private void A() {
_Central.WorkItAsync(5, new AsyncCallback(B));
}
private void B(object test) {
// do something with the result
}
}
UPDATE: I've managed to wrap my IMyService into my ICentralService and pass the result from WCF (IMyService) to my viewmodel via ICentralService.
First attempt/idea, but this did not return my "dataResult" value to my viewmodel:
public void WorkItAsync(int x, AsyncCallback callback)
{
var task = Task<int>.Factory.StartNew(() =>
{
int dataResult = -1;
_Srv.BeginTest(x, (ar) => {
dataResult = _Srv.EndTest(ar);
}, null);
return dataResult ;
});
if (callback != null)
task.ContinueWith((t) => callback(t));
return task;
}
Second attempt (works):
public void WorkItAsync(int x, AsyncCallback callback)
{
TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
Task<int> t1 = tcs1.Task;
Task<int>.Factory.StartNew(() =>
{
int dataResult = -1;
_Srv.BeginTest(x, (ar) => {
dataResult = _Srv.EndTest(ar);
tcs1.SetResult(dataResult);
}, null);
return dataResult;
});
if (callback != null)
t1.ContinueWith((t) => callback(t));
return t1;
}
I'm not sure if this is a good solution using the TaskCompletionSource, but for now it seems to works. (Too bad I have to return a useless -1 dataResult value).
Upvotes: 0
Views: 711
Reputation: 8005
The second update looks pretty good, but I think you can use TaskFactory.FromAsync to avoid the TaskCompletionSource for now. Here is a reference page for FromAsync with examples. I haven't tested this, but the method may look something like this:
public interface ICentralService
{
// Just use .ContinueWith to call a completion method
Task<int> WorkItAsync(int x);
}
public class CentralService : ICentralService
{
private IMyService _Srv;
public CentralService(IMyService srv)
{
_Srv = srv;
}
public Task<int> WorkItAsync(int x)
{
// Callback is handled in ViewModel using ContinueWith
return Task<int>.Factory.FromAsync(_Src.BeginTest, _Src.EndTest, x);
}
}
public class SomeViewModel : INotifyPropertyChanged
{
private ICentralService _Central;
public SomeViewModel(ICentralService central)
{
_Central = central;
}
private void A()
{
_Central.WorkItAsync(5)
.ContinueWith(prevTask =>
{
// Handle or throw exception - change as you see necessary
if (prevTask.Exception != null)
throw prevTask.Exception;
// Do something with the result, call another method, or return it...
return prevTask.Result;
});
}
}
Upvotes: 2
Reputation: 1082
You could use lambdas:
public void WorkItAsync(int x, AsyncCallback callback)
{
// callback is the callback from my viewmodel
_Srv.BeginTest(x, ar=>{
int result = _Srv.EndTest(ar);
callback(ar);
});
}
ar will be your IAsyncResult if needed. You could even run multiple requests in parallel, because the callback variable will be in local scope for each of the parallel calls.
Upvotes: 1