2
votes

I am migrating my Windows Phone App to Windows Universal Apps. In Phone App, I used BackgroundWorker for database retrieval and then show in UI. Below is the class which I prepared in Windows Phone 8 and how it was called.

public class TestBackgroundWorker
{
    private BackgroundWorker backgroundWorker;
    ProgressIndicator progressIndicator;

    public delegate void functionToRunInBackground();
    public functionToRunInBackground currentFunctionToExecute;

    public delegate void callbackFunction();
    public callbackFunction functionToSendResult;

    private bool isCancellationSupported;


    private string message;


    /// <summary>
    /// 
    /// </summary>
    /// <param name="functionNameToExecute">specifies function name to be executed in background</param>
    /// <param name="isCancellable">Flag which specifies whether the operation is cancellable or not</param>
    /// <param name="functionNameWhichGetsResult">Specifies call back function to be executed after the completion of operation</param>
    public MCSBackgroundWorker(functionToRunInBackground functionNameToExecute, bool isCancellable, string messageToDisplay, callbackFunction functionNameWhichGetsResult)
    {
        currentFunctionToExecute = functionNameToExecute;
        functionToSendResult = functionNameWhichGetsResult;
        isCancellationSupported = isCancellable;
        message = messageToDisplay;
        backgroundWorker = new BackgroundWorker();
        backgroundWorker.WorkerSupportsCancellation = isCancellable;
        backgroundWorker.DoWork += backgroundWorker_DoWork;
        backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
    }

    void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        deactivateProgressIndicator();
        functionToSendResult();
    }

    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (currentFunctionToExecute != null)
        {
            currentFunctionToExecute();
        }
    }


    public void cancelBackgroundOperation()
    {
        if (isCancellationSupported == true)
        {
            backgroundWorker.CancelAsync();
        }
    }


    public void Start()
    {
        backgroundWorker.RunWorkerAsync();
        activateProgressIndicator();
    }


    void activateProgressIndicator()
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            var currentPage = App.RootFrame.Content as PhoneApplicationPage;
            SystemTray.SetIsVisible(currentPage, true);
            SystemTray.SetOpacity(currentPage, 0.5);
            SystemTray.SetBackgroundColor(currentPage, Colors.White);
            SystemTray.SetForegroundColor(currentPage, Colors.Black);

            progressIndicator = new ProgressIndicator();
            progressIndicator.IsVisible = true;
            progressIndicator.IsIndeterminate = true;
            progressIndicator.Text = message;

            SystemTray.SetProgressIndicator(currentPage, progressIndicator);

        });

    }

    void deactivateProgressIndicator()
    {
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (progressIndicator != null)
            {
                var currentPage = App.RootFrame.Content as PhoneApplicationPage;
                progressIndicator.IsVisible = false;
                SystemTray.SetIsVisible(currentPage, false);

            }

        });
    }


    public bool isBackgroundWorkerBusy()
    {
        return backgroundWorker != null ? backgroundWorker.IsBusy : false;
    }

}

}

And calling that as below to run the process in background.

private void loadReports()
    {
        bgWorker = new TestBackgroundWorker(loadReportsFromDB, true, "Loading...", showReports);
        bgWorker.Start();
    }

Here, loadReprtsFromDB and showReports are two functions.

Questions:

  1. Can anyone suggest how to achieve same thing in Windows 8.1?

  2. Is there any alternative for PhoneApplicationService.Current.State?

1

1 Answers

2
votes

IMHO, even for the desktop, the Task<T> and Progress<T> classes offer a nice alternative to BackgroundWorker, and they are both supported on Windows Phone 8.1. The Task<T> class provides the mechanism to start and then cleanly wait for background operations, while the Progress<T> class provides the mechanism for reporting progress (not part of your example or question, but I mention it because that's the one thing Task along with async/await doesn't provide from BackgroundWorker).

Your example could be changed to something like this:

public class TestBackgroundWorker
{
    private Task _task;
    private CancellationTokenSource _cancelSource;

    public CancellationToken CancellationToken
    {
        get { return _cancelSource != null ? _cancelSource.Token : null; }
    }

    ProgressIndicator progressIndicator;

    public readonly Action<TestBackgroundWorker> currentFunctionToExecute;

    private string message;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="functionNameToExecute">specifies function name to be executed in background</param>
    /// <param name="isCancellable">Flag which specifies whether the operation is cancellable or not</param>
    /// <param name="functionNameWhichGetsResult">Specifies call back function to be executed after the completion of operation</param>
    public MCSBackgroundWorker(Action<TestBackgroundWorker> functionNameToExecute, bool isCancellable, string messageToDisplay)
    {
        currentFunctionToExecute = functionNameToExecute;
        _cancelSource = isCancellable ? new CancellationTokenSource() : null;
        message = messageToDisplay;
    }

    public void cancelBackgroundOperation()
    {
        if (_cancelSource != null)
        {
            _cancelSource.Cancel();
        }
    }

    public async Task Start()
    {
        activateProgressIndicator();
        _task = Task.Run(() => currentFunctionToExecute(this));
        await _task;
        _task = null;
        deactivateProgressIndicator();
    }

    void activateProgressIndicator()
    {
        // In theory, you should not need to use Dispatcher here with async/await.
        // But without a complete code example, it's impossible for me to
        // say for sure, so I've left it as-is.
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            var currentPage = App.RootFrame.Content as PhoneApplicationPage;
            SystemTray.SetIsVisible(currentPage, true);
            SystemTray.SetOpacity(currentPage, 0.5);
            SystemTray.SetBackgroundColor(currentPage, Colors.White);
            SystemTray.SetForegroundColor(currentPage, Colors.Black);

            progressIndicator = new ProgressIndicator();
            progressIndicator.IsVisible = true;
            progressIndicator.IsIndeterminate = true;
            progressIndicator.Text = message;

            SystemTray.SetProgressIndicator(currentPage, progressIndicator);
        });
    }

    void deactivateProgressIndicator()
    {
        // Likewise.
        Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
            if (progressIndicator != null)
            {
                var currentPage = App.RootFrame.Content as PhoneApplicationPage;
                progressIndicator.IsVisible = false;
                SystemTray.SetIsVisible(currentPage, false);
            }
        });
    }

    public bool isBackgroundWorkerBusy()
    {
        return _task != null;
    }
}

Then you could use it something like this:

private async Task loadReports()
{
    bgWorker = new TestBackgroundWorker(loadReportsFromDB, true, "Loading...");
    await bgWorker.Start();
    showReports();
}

void loadReportsFromDB(TaskBackgroundWorker worker)
{
    while (...)
    {
        if (worker.CancellationToken.IsCancellationRequested)
        {
            return; // or whatever
        }
    }
}

To deal with cancellation, the functionNameToExecute delegate would need to be for a method that accepts an instance of TaskBackgroundWorker as a parameter, so that it can retrieve the CancellationToken property value to check for cancellation (similar to the DoWork() event handler…though your code example didn't actually suggest any mechanism by which the actual background operation code would even detect cancellation).

Note that with async/await, your task can also return a value if you like, via the Task<T> type instead of Task. The above example could easily be modified to accommodate that, and that feature of async/await is one of the biggest reasons I prefer it over BackgroundWorker (which has no clean, compiler-supported mechanism for returning results from the background operation).

Caveat: Lacking a complete code example to start with, there is no point for me to try to actually compile and test any of the code. So the above is strictly "browser-authored". It should suffice for the purposes of illustration, but I apologize in advance for any typos that might exist.