开发者

Silverlight, dealing with Async calls

开发者 https://www.devze.com 2022-12-21 06:52 出处:网络
I have some code as below: foreach (var position in mAllPositions) { DoAsyncCall(position); } //I want to execute code here after each Async call has finished

I have some code as below:

        foreach (var position in mAllPositions)
        {
                 DoAsyncCall(position);
        }
//I want to execute code here after each Async call has finished

So how can I do the above?

I could do something like:

开发者_如何转开发        while (count < mAllPositions.Count)
        { 
            //Run my code here
        }

and increment count after each Async call is made ... but this doesn't seem like a good way of doing it

Any advice? Is there some design pattern for the above problem as i'm sure it's a common scenario?


Given that you're using Silverlight, and you don't have access to Semaphores, you might be looking for something like this (written in notepad, so no promises as to perfect syntax):

int completedCallCount = 0;
int targetCount = mAllPositions.Count;

using (ManualResetEvent manualResetEvent = new ManualResetEvent(false))
{
    proxy.DoAsyncCallCompleted += (s, e) =>
    {
        if (Interlocked.Increment(ref completedCallCount) == targetCount)
        {
            manualResetEvent.Set();
        }
    };

    foreach (var position in mAllPositions)
    {
        proxy.DoAsyncCall(position);
    }

    // This will wait until all the events have completed.
    manualResetEvent.WaitOne();
}

However, one of the benefits of Silverlight forcing you to asynchronously load data is that you have to work hard to lock the UI up when making a service calls, which is exactly what you're going to do with code like this, unless you have it running on a background thread, which I strongly recommend you do. You can always display a "please wait" message until the background process completes.


(NB, I am giving you two separate answers. This one sticks as close to your question as possible.)

Your question says

while{ count >= mAllPositions.Count )
{
    //Run my code here
}

but I am guessing that what you really mean is:

while( count < mAllPositions.Count )
   ; // do nothing -- busy wait until the count has been incremented enough
// Now run my code here

??

If so, then you can accomplish the same thing more efficiently by using a semaphore:

Semaphore TheSemaphore = new Semaphore( 0, mAllPositions.Count );

As each async call completes, release the semaphore.

TheSemaphore.Release();

Before executing your final code, ensure the semaphore has been released the requisite number of times:

 for( int i=0; i<mAllPositions.Count; i++ )
    TheSemaphore.WaitOne();
 // Now run follow-on code.

Your code will block until the async operations have all been completed.


(NB, I am giving two separate answers; this takes a totally different approach than in the question.)

Following the code sample by Miguel Madero at this URL, use the Reactive Framework to wait in parallel for all the results to complete, then follow on with another task.

(There's other discussion in the same email chain, including a link to this closely related StackOverflow question.)


Your question is a little unclear (should it be count <=?), but here goes...

What you are asking for is how to do a synchronous call. With an async call, before you make the call you assign an event handler to be called upon completion of the call, then you make the call. This means your completion code is in a different function to the code that makes the call. This means that if your async call is made on the UI thread, the UI will not block while the call is made.

Sync calls are possible in Silverlight, but you have to make sure you do not do them on the UI thread. One way to achieve this is to start a new background thread, execute the async call on the background thread, but block the return until the call has been completed. Here is a pseudo code sample:

private AutoResetEvent myResetEvent;

private void MyCallFunction(object someParameter) {

    if (this.Dispatcher.CheckAccess())
    {
        Action<object> a = new Action<object>(MyCallFunction);
        a.BeginInvoke(someParameter, null, null);
        return;
    }

    myResetEvent = new AutoresetEvent();
    myAsyncCall.CallCompleted += new EventHandler<>(myAsyncCall_CallCompleted);
    myAsyncCall.DoAsyncCall(someParameter);

    myResetEvent.WaitOne();
    //increment your count here
}

private void myAsyncCall_CallCompleted(object sender, SomeEventArgs e) {
    if (e.Error == null && !e.Cancelled) {
        if (myResetEvent != null)
            myResetEvent.Set();
    }
}

Note that this code is not particularly thread safe or production ready - it is just a quick sample.

What happens here is that when you enter MyCallFunction, it checks to see if it is running on the UI thread, if it is then it re-invokes itself on a background thread. It then sets up an AutoResetEvent, and makes the async call. It then pauses on the myResetEvent object until it has been set (or 'signalled') from the call completed handler, at which point code execution continues. Note that you shouldn't attempt to access a control directly from code like this without first ensuring you are back on the UI thread.

When i started working out how to do this in Silverlight, i started with this SO post, and continued with this link. But as Marc gravell says in that first post, don't do it if you can avoid it. The only time i have needed to do this was when i needed to aggregate the results of several disparate WCF calls into one result that was returned to the UI (and these calls could not be combined in to one facade style WCF method).


You can do great things by following the concept of Async workflows (navigate to the C# paragraph) as depicted via Tomas' blog.

http://tomasp.net/blog/csharp-async.aspx


  1. Setup a static autoResetEvent
  2. Setup ThreadPoolQueueUserWorkItems
  3. ForEach item in the loop, queue the item, and pass in the autoResetEvent as the callback's parameter.
  4. Within the callback, do the work and then call the autoresetevent.Set();
  5. In the mainbody at the point of //I want to execute code here after each Async call has finished call the appropriate autoResetEvent.Wait();


Another option is to increase a counter in the foreach loop that calls the async processes. Then in the callback check you will decrement the counter and check for equality to zero. When the counter has returned to zero all calls are complete and your next code can run. Just be sure that you change the counter in a thread safe manner. The easiest way to do this might be to return to the UI thread before you decrement and test the counter.


I don't see this as a "I want to do a synchronous call" problem. All you're really doing is tying a series of async calls together when they are all complete.

I have encountered this before in the case where you have a list of items and need to asynchronously get attributes for them from the server (some for of status for instance) but also want to update some loading progress indicator on the screen.

In this situation you don't want to block the UI thread while all of the items are being updated (blocking threads is evil anyway if there is an alternative).

So I used a control class like this:

public class GroupAction
{
    public delegate void ActionCompletion();

    private uint _count = 0;
    private ActionCompletion _callback;
    private object _lockObject = new object();

    public GroupAction(uint count, ActionCompletion callback)
    {
        _count = count;
        _callback = callback;
    }

    public void SingleActionComplete()
    {
        lock(_lockObject)
        {
            if (_count > 0)
            {
                _count--;
                if (_count == 0) _callback();
            }
        }
    }
}

You create this action with a count (for the number of items) and a callback that gets executed on completion of all the items. Basically like a semaphore with more control and no threading to worry about.

The calling code would look something like this (I have based the example on calling the standard temp conversion web service you can find at w3schools.com).

    private void DoGroupCall()
    {
        uint count = 5;
        GroupAction action = new GroupAction(count,
            () =>
            {
                MessageBox.Show("Finished All Tasks");
            });

        for (uint i = 0; i < count; i++)
        {
            TempConvertHttpPost proxy = new TempConvertHttpPostClient(new BasicHttpBinding(), new EndpointAddress("http://localhost/webservices/tempconvert.asmx"));
            CelsiusToFahrenheitRequest request = new CelsiusToFahrenheitRequest() { Celsius = "100" };

            proxy.BeginCelsiusToFahrenheit(request,
                  (ar) => Deployment.Current.Dispatcher.BeginInvoke(
                      () =>
                      {
                          CelsiusToFahrenheitResponse response = proxy.EndCelsiusToFahrenheit(ar);

                          // Other code presumably...

                          action.SingleActionComplete();
                      }
                  ), null);
        }
    }

Note how the GroupAction instance is defined with an inline callback delegate and then the SingleCallBack function is called each time the service returns.

Hope this helps...

0

精彩评论

暂无评论...
验证码 换一张
取 消