4
votes

In my windows phone 8.1 application I have a singleton service DataService which should once in a while be downloading some data. Meanwhile on UI I should be displaying the amount of data received. DataService.StartGettingData() gets called when user logs into the application:

void StartGettingData()
{
    if (getDataTaskCancellationTokenSource != null)
        getDataTaskCancellationTokenSource.Cancel();

    getDataTaskCancellationTokenSource = new CancellationTokenSource();
    var token = getDataTaskCancellationTokenSource.Token;

    Task.Factory.StartNew(async () => await ExecuteCycleAsync(token), token);
}

async Task ExecuteCycleAsync(CancellationToken cancellationToken)
{
    while (true)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await LoadDataAsync(cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
        await Task.Delay(timeTillNextDownload, cancellationToken);
    }
}

This task will be cancelled when user logs out with the help of

if (getDataTaskCancellationTokenSource != null)
    getDataTaskCancellationTokenSource.Cancel();

The property containing the result of download looks like this:

List<DataType> Data = new List<DataType>();
public IEnumerable<DataType> Data
{
    get { return Data; }
    set
    {
        Data = value.ToList();
        OnDataUpdated();
    }
}

void OnDataUpdated()
{
    var handler = DataUpdated;
    if (handler != null)
        handler(this, EventArgs.Empty);
}

This part seemed to be working until I had to display the amount of data on the screen. My MainViewModel gets instance of DataService injected with Ninject.

readonly IDataService DataService;

public MainViewModel(IDataService dataService)
{
    DataService = dataService;
    DataService.DataUpdated += DataService_DataUpdated;
    UpdateDataCount();
}

void DataService_DataUpdated(object sender, EventArgs e)
{
    UpdateDataCount();
}

void UpdateDataCount()
{
    DataCount = DataService.Data.Count();
}

In xaml I've got TextBlock binded to DataCount property of MainViewModel

int DataCount;
public int DataCount
{
    get { return DataCount; }
    set
    {
        DataCount = value;
        OnPropertyChanged();
    }
}

protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

And here is where problem appears: OnPropertyChanged fails with "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" which get's caught in DataService.LoadDataAsync(). I understand the runtime is trying to tell me I am accessing UI element from non ui thread. But am I? I thought OnPropertyChanged is the magic place which disconnects UI from the rest of background tasks. Of course, the problem can be solved implementing OnPropertyChanged this way:

public CoreDispatcher Dispatcher { get; set; }

protected async void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        });
    }
}

But should it really be implemented this way? Or am I missing something in DataService.ExecuteCycleAsync()?

1
where do you create a new instance of your view model and how is it getting hooked up to your xaml?Tejas Sharma
Whether you are running in a separate thread depends on where OnDataUpdated is called from. I suspect it is in the LoadDataAsync method which you have ommited and if so, then yes, it is in a separate thread. There is no magic to make the UI thread proof so your approach is correct.Kell
@Kell, OnDataUpdated is called inside IEnumerable<DataType> Data, so I suppose it is being run on background thread.foxanna
@TejasSharma My xaml Page's DataContext is bound to ViewModel property: DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}" and it's value is initialized through Ninject as well: ViewModel = NinjectKernel.Get(TypeOfViewModel)foxanna

1 Answers

7
votes

Not trying to dig any deeper, I believe your problem is this:

Task.Factory.StartNew(async () => await ExecuteCycleAsync(token), token);

Change it to simply this, see if it works as expected:

ExecuteCycleAsync(token);

Otherwise, the code inside ExecuteCycleAsync starts execution on a thread without synchronization context, which can lead to all different kinds of problems, depending on what's inside LoadDataAsync.

Note that calling ExecuteCycleAsync(token) like this is still a fire-and-forget call which may not be observing any exceptions (more here). You may want to store the Task object it returns, to be able to observe it later.