Search code examples
c#uwpinterfacetask

Tasks and Interface in UWP


I'm building an app that uploading files to Google Drive. WHen I'm binding properties and inoked them, it crash with this error:

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

my code ->

MyFiles class:

propfull Progress<IUploadProgress> progress;
propfull double Value;
propfull string Name;
propfull double Size;
propfull string fullpath;

//each one of properties has `OnPropertyChanged()` in SET
//More properties...

public virtual void OnPropertyChange([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    Debug.WriteLine($"~~~~~~{propertyName} invoked!");
}

public MyFiles(StorageFile file)
{
    FileInfo fileInfo = new FileInfo(file.Path);
    this.fullpath = file.Path
    this.Size = fileInfo.Length;
    this.Value = 0;
    this.Name = file.name;
    this.progress = new Progress<IUploadProgress>();
    this.progress += progressChanged; //prog.Report() is calling this
}

private void progressChanged(object sender, IUploadProgress e)
{
    if (e.Status == UploadStatus.Uploading)
    {
        this.Value = (e.BytesSent * 100) / this.Size; 
        //invoke(this.value) property.
        //Binding to ProgressBar.Value
    }
}

the MediaPage.xaml file:

<ListView x:Name="myListView" Grid.Row="1" Grid.Column="1" Height="600" Width="700">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid Height="70">
                <StackPanel Orientation="Horizontal">
                    <Grid>
                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="10,20" FontSize="20"  Grid.Row="0" FontFamily="Yu Gothic UI Semibold"/>
                        <ProgressBar Value="{Binding Value}" Tag="{Binding Name}" Height="5" Width="200"/>
                    </Grid>
                </StackPanel>
            </Grid>

        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
<Button x:Name="upload_btn" Click="ButtonClick" Height="40" Width="150" Content="Upload All Files"/>

the code in MediaPage.xaml.cs

public MediaPage()
{
    this.InitializeComponent();
    SavedData.files = new ObservableCollection<MyFiles>();
    StorageFile file = ...; // testing file
    SavedData.files.Add(new MyFiles(file));

    myListView.ItemsSource = SavedData.files;
}

private void ButtonClick(object sender, RoutedEventArgs e)
{
    for (int i = 0; i < SavedData.files.Count; i++)
    {
        DriveClass.UploadFile(i, SavedData.files[i].progress); //(index,progress)
    }
}


the DriveClass code:

public void UploadFile(int index, IProgress<IUploadProgress> prog)
{

    try
    {
        StorageFile file = StorageFile.GetFileFromPathAsync([SavedData.files[index].fullpath).AsTask().Result;

        var fileMetadata = new Google.Apis.Drive.v3.Data.File()
        {
            Name = Path.GetFileName(file.Name),
            MimeType = "application/octet-stream"
            
        };

        using (var stream = file.OpenStreamForReadAsync().Result)
        {
            

            var request = this.drive.Files.Create(fileMetadata, stream, "application/octet-stream");

            request.ProgressChanged += (IUploadProgress progressInfo) =>
            {
                if (progressInfo.Status == UploadStatus.Completed)
                {
                    Debug.WriteLine($"done!");
                    prog.Report(progressInfo);
                }
                else if (progressInfo.Status == UploadStatus.Failed)
                {
                    Debug.WriteLine($"Failed To Upload Into Google Drive");
                    prog.Report(progressInfo);
                }
                else
                {
                    Debug.WriteLine(progressInfo.BytesSent + " has sent");
                    prog.Report(progressInfo);

                }
            };

            request.ChunkSize = 262144;
            request.Upload();

            var uploadedFile = request.ResponseBody;
            Debug.WriteLine($"File uploaded: {uploadedFile.Name} ({uploadedFile.Id})");

        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Cant uplaod the file because -> " + ex.Message);
    }
}

this is the whole cade. everything works fine until I press the button and calling to UploadFile().

In UploadFile(), when the request.progressChanged is active, it called to files[i].progress.ProgressChanged().

ProgressChanged() is updating the files[i].Value and invoke() to change the ProgressBar.Value = "{Binding Value}"

the app crash in the Invoke() line, because I can't update the UI.


Solution

  • After searching, I found something that helped.

    the way to Invoke the properties that the UI is connected to, is to add this kind of dispatcher to OnPropertyChanged().

    public async virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        });
    
    }
    

    After adding this Dispatcher, the code was running but the app was stuck. so, I turn the UploadFile() to Task instead of void and added the folowing code to ButtonClick() event in MediaPage.xaml.cs

    Task UploadFileTask = Task.Run(() => DriveClass.UploadFile(index,progress));
    

    This also will continue the uploading progress.

    Here how the ProgressBar looks and what I wanted to do

    I saw the answer in this question.