Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create UserControl in non-UI thread Silverlight 5 browser application

I have a Silverlight 5 browser application.

There is a class

public class ActivityControl:UserControl {

    public void LoadSubControls() {
        //Creates Other UserControls, does calculations and is very slow..No refactoring..
    }
}

I need to create multiple instances of this class and call the method LoadSubControls on runtime.

public class BasicContainer:UserControl  {

    public void CreateMultipleActivityControls() {

        for (int i = 0; i < 1000; i++) {

            ActivityControl c = new ActivityControl();  ====> I need to call this in a different thread but causes Invalid Cross Thread Exception

            c.LoadSubControls();
        }
    }
}

Is there any way to create multiple UI Threads in order to avoid invalid cross thread exception?

I need multithreading for performance reasons and because the method call is very slow and the UI freezes.

Is there any way to call method SetSyncronizationContext (which is [SecurityCritical]) in Silverlight?

like image 214
George Avatar asked Sep 22 '16 14:09

George


1 Answers

There is no avoiding creating these controls on the UI thread, but you could take advantage of System.Threading.Tasks.Task from Task Parallel Library (TPL) to allow for asynchronous operations.

I've been able to do something like this in silverlight 5 with a structure like this. Got the original idea looking at the source for Caliburn.Micro.

The following is a subset that applies to what you want.

public interface IPlatformProvider {
    /// <summary>
    ///  Executes the action on the UI thread asynchronously.
    /// </summary>
    /// <param name = "action">The action to execute.</param>
    System.Threading.Tasks.Task OnUIThreadAsync(Action action);    
}

Here is the implementation.

/// <summary>
/// A <see cref="IPlatformProvider"/> implementation for the XAML platfrom (Silverlight).
/// </summary>
public class XamlPlatformProvider : IPlatformProvider {
    private Dispatcher dispatcher;

    public XamlPlatformProvider() {
       dispatcher = System.Windows.Deployment.Current.Dispatcher;
    }

    private void validateDispatcher() {
        if (dispatcher == null)
            throw new InvalidOperationException("Not initialized with dispatcher.");
    }

    /// <summary>
    ///  Executes the action on the UI thread asynchronously.
    /// </summary>
    /// <param name = "action">The action to execute.</param>
    public Task OnUIThreadAsync(System.Action action) {
        validateDispatcher();
        var taskSource = new TaskCompletionSource<object>();
        System.Action method = () => {
            try {
                action();
                taskSource.SetResult(null);
            } catch (Exception ex) {
                taskSource.SetException(ex);
            }
        };
        dispatcher.BeginInvoke(method);
        return taskSource.Task;
    }
}

You could either go down the constructor DI route to pass in the provider or use a static locator pattern like this.

/// <summary>
/// Access the current <see cref="IPlatformProvider"/>.
/// </summary>
public static class PlatformProvider {
    private static IPlatformProvider current = new XamlPlatformProvider();

    /// <summary>
    /// Gets or sets the current <see cref="IPlatformProvider"/>.
    /// </summary>
    public static IPlatformProvider Current {
        get { return current; }
        set { current = value; }
    }
}

Now you should be able to make your calls without blocking the main thread and freezing the UI

public class BasicContainer : UserControl {
    public async Task CreateMultipleActivityControls() {
        var platform = PlatformProvider.Current;
        for (var i = 0; i < 1000; i++) {
            await platform.OnUIThreadAsync(() => {    
                var c = new ActivityControl();     
                c.LoadSubControls();
            });    
        }
    }
}

if making multiple calls to the dispatcher caused any performance issues you could instead move the entire process to one acync call.

public class BasicContainer : UserControl {
    public async Task CreateMultipleActivityControls() {
        var platform = PlatformProvider.Current;
        await platform.OnUIThreadAsync(() => {
            for (var i = 0; i < 1000; i++) {                    
                var c = new ActivityControl();     
                c.LoadSubControls();
            }    
        });
    }
}
like image 67
Nkosi Avatar answered Sep 30 '22 06:09

Nkosi