0

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).

juFo
  • 17,849
  • 10
  • 105
  • 142
  • You may not be using .NET 4.5 with `await`, but you could still use `Task` and return Tasks from the service and `ContinueWith` for continuations. IMO `Task` is easier to use than `IAsyncResult` and provides a better bridge to the awaitable API when your solution migrates to .NET 4.5+. – Ryan Mar 18 '15 at 10:29
  • 2
    Yes, you can! http://stackoverflow.com/questions/9110472/using-async-await-on-net-4 – D.Rosado Mar 18 '15 at 10:43
  • @D.Rosado nope... VS2010. – juFo Mar 18 '15 at 10:44
  • 1
    @juFo: You might want to point out to management that your development tools are *half a decade* old, and you're about to be *three major versions* behind. – Stephen Cleary Mar 18 '15 at 12:50
  • @StephenCleary working on that ;-) #nextmonth – juFo Mar 18 '15 at 12:55

2 Answers2

2

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;
                });
    }
}
Ryan
  • 7,835
  • 2
  • 29
  • 36
1

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.

Holger Thiemann
  • 1,042
  • 7
  • 17
  • isn't that dangerous to send an Action or Func via wcf? – juFo Mar 18 '15 at 11:06
  • You can't send an Action or Func via Wcf. The Callback is not sent to the server. It is stored internally in the client until the response of the server arrives and than called. Otherwise your code would send it, too. – Holger Thiemann Mar 18 '15 at 14:08
  • good answer, but changing to the method of @Ryan with Tasks and ContinueWith now. – juFo Mar 18 '15 at 15:56