该应用程序调用了一个为不同线程编组的接口.(来自HRESULT的异常:0x8001010E(RPC_E_WRONG_THREAD))

fox*_*nna 4 c# multithreading async-await windows-phone-8.1

在我的Windows Phone 8.1应用程序中,我有一个单件服务DataService,应该偶尔下载一些数据.同时在UI上我应该显示收到的数据量.当用户登录应用程序时,将调用DataService.StartGettingData():

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当用户在帮助下注销时,此任务将被取消

if (getDataTaskCancellationTokenSource != null)
    getDataTaskCancellationTokenSource.Cancel();
Run Code Online (Sandbox Code Playgroud)

包含下载结果的属性如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

这部分似乎一直在工作,直到我必须在屏幕上显示数据量.我的MainViewModel获取了使用Ninject注入的DataService实例.

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();
}
Run Code Online (Sandbox Code Playgroud)

在xaml中,我将TextBlock绑定到MainViewModel的DataCount属性

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这里出现问题:OnPropertyChanged失败,"应用程序调用了一个为不同线程编组的接口.(来自HRESULT的异常:0x8001010E(RPC_E_WRONG_THREAD))",它被DataService.LoadDataAsync()捕获.我理解运行时试图告诉我我从非ui线程访问UI元素.但是我呢?我认为OnPropertyChanged是将UI与其他后台任务断开连接的神奇之处.当然,这个问题可以通过这种方式实现OnPropertyChanged来解决:

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));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

但它应该以这种方式实现吗?或者我在DataService.ExecuteCycleAsync()中遗漏了什么?

nos*_*tio 7

不想深入挖掘,我相信你的问题是这样的:

Task.Factory.StartNew(async () => await ExecuteCycleAsync(token), token);
Run Code Online (Sandbox Code Playgroud)

将其更改为简单,看看它是否按预期工作:

ExecuteCycleAsync(token);
Run Code Online (Sandbox Code Playgroud)

否则,内部代码在ExecuteCycleAsync没有同步上下文的线程上开始执行,这可能导致所有不同类型的问题,具体取决于内部的内容LoadDataAsync.

请注意,ExecuteCycleAsync(token)像这样调用仍然是一个即发即弃的调用,可能无法观察到任何异常(此处更多).您可能希望存储Task它返回的对象,以便以后能够观察它.