Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

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()?

like image 809
foxanna Avatar asked Mar 17 '23 19:03

foxanna


1 Answers

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.

like image 115
noseratio Avatar answered Mar 20 '23 18:03

noseratio