C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

DropBoxAPIでのアップロードを失敗する。

[トピック内 7 記事 (1 - 7 表示)]  << 0 >>

■103420 / inTopicNo.1)  DropBoxAPIでのアップロードを失敗する。
  
□投稿者/ 高橋 (3回)-(2024/11/06(Wed) 12:59:17)

分類:[C#] 

開発環境:Microsoft Visual Studio Professional 2022 (64 ビット) - Current Version 17.8.6
使用言語:C#
フレームワーク:.Net8.0


昨晩投稿したつもりが投稿できていなかったので再投稿します。
もし重複していたら片方後日削除いたします。

DropBoxAPIを用いてファイルをアップロードする処理を書いています。
ただ、エラーを吐いてしまいうまくアップロードができません。
それについて3点ご教示いただけないでしょうか。
(1)このエラーの原因と対処法
(2)エラーメッセージのどこを見てどういう情報を調べれば独学でエラーを直せたか。
(3)内部例外2は内部例外1が原因で引き起こされているという理解でよいか。

【エラー内容】
System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred. (Error while copying content to a stream.)
(中略)
  この例外は、最初にこの呼び出し履歴 
    [外部コード] でスローされました

内部例外 1:
HttpRequestException: Error while copying content to a stream.

内部例外 2:
ObjectDisposedException: Cannot access a closed Stream.


【ソースコード】 ※一部投稿用に改編
private void button1_Click(object sender, EventArgs e)
{
    //HttpWebRequestでエラーが出る原因にTLS1.2が有効でないことがあるという記事を見たため。(多分関係ないっぽい)
    System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
    using DropBoxAPI dropBoxAPI = new();
    {
        Task noWaitTask = dropBoxAPI.UploadFile("C:\\Users\\(ユーザー名)\\Desktop\\test.txt");
        //この下の行でエラーを吐きます。
        noWaitTask.Wait();
    }
}

public class DropBoxAPI : IDisposable
{
    private const string token = "(4時間限定トークン)";
    private readonly DropboxClient _DropBoxClient;

    public DropBoxAPI()
    {
        _DropBoxClient = new DropboxClient(token);
    }
    public Task UploadFile(string filePath)
    {
        string fileName = Path.GetFileName(filePath);
        using MemoryStream memoryStream = new(File.ReadAllBytes(filePath));
        return  _DropBoxClient.Files.UploadAsync("/"+ fileName, body: memoryStream);
    }
    //Dispose関連の処理(投稿省略)
}

【原因と疑った点】
(1)DropBoxのAPIの権限を適切に有効にしているか。 ⇒ とりあえずほぼすべてのWriteRead権限を設定する、アプリフォルダを設定するなど適切に設定したつもりです。、
(2)DropBoxのAPIで使用するトークンは4時間しか有効ではないのではないか。 ⇒ とりあえずテスト時点ではトークンを発行しなおしてすぐにコピペしています。将来的にはちゃんと考えないといけないですが。
(3)ファイルが存在しないとかいうミスはないか、 ⇒ 存在するファイルパスを指定しています。ファイルを消すとエラーを吐くのがMemoryStream行になるから多分適切にパスは指定しています。
(4)MemoryStreamじゃなくてFileStreamじゃないのか。 ⇒ 前者はメモリ、後者はファイルを取り扱うstreamで文法がちょっと違うということ以外よくわかっていませんがどちらでも試してはみました。
(5)async await waitの使い方がおかしくないか。 ⇒ 同期処理と非同期処理をよく理解していないのでここは本当によくわからずに書いています。でもデッドロックとかではなく、エラーを吐くので同期/非同期が原因ではないと思っています。

引用返信 編集キー/
■103421 / inTopicNo.2)  Re[1]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ 魔界の仮面弁士 (3810回)-(2024/11/06(Wed) 14:14:11)
2024/11/06(Wed) 14:31:48 編集(投稿者)

No103420 (高橋 さん) に返信
> DropBoxAPIを用いてファイルをアップロードする処理を書いています。
DropBox.Api のことでしょうか。
https://www.dropbox.com/developers/documentation/dotnet#install
https://www.nuget.org/packages/Dropbox.Api/7.0.0


> public Task UploadFile(string filePath)
> {
async Task<FileMetadata> UploadFileAsync(
にはしないのでしょうか?


> using DropBoxAPI dropBoxAPI = new();
> {
> Task noWaitTask = dropBoxAPI.UploadFile("C:\\Users\\(ユーザー名)\\Desktop\\test.txt");
> noWaitTask.Wait();
> }
非同期処理の完了を await せずに Dispose したらマズイと思いますよ。
このコードでは、アップロードが完了する前に Dispose されてしまう可能性があるかと。

それと、波括弧の扱いが奇妙に見えます。文法上は間違いでは無いのですが、
使うのが「using」であるのなら、丸括弧を併用して
 using (DropBoxAPI dropBoxAPI = new())
 {
  dropBoxAPI.Upload(〜); // 非同期メソッドではない
 }
とするか、もしくは波括弧無しで
 using DropBoxAPI dropBoxAPI = new();
 dropBoxAPI.Upload(〜); // 非同期メソッドではない
とする方が自然でしょう。

非同期の場合は
 DropBoxAPI dropBoxAPI = new();
 await using (dropBoxAPI.ConfigureAwait(false))
 {
  // 非同期処理
 }
ですかね。async using にするのであれば IAsyncDisposable を実装するか、
単に DisposeAsync メソッドを追加する必要があります。


あるいは、using に頼らなくても済むように、File クラスの
 await File.WriteAllTextAsync(filePath, text);
のように、アップロードまでの一連の処理を、単一の静的メソッドに押し込める手も。
引用返信 編集キー/
■103422 / inTopicNo.3)  Re[1]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ Hongliang (1304回)-(2024/11/06(Wed) 14:17:33)
> (1)このエラーの原因と対処法

> 内部例外 2:
> ObjectDisposedException: Cannot access a closed Stream.

既に閉じられてるストリームにアクセスできないって言っていますね。
じゃあどこでストリームを使っていていつ閉じているかという話ですが、

> using MemoryStream memoryStream = new(File.ReadAllBytes(filePath));
> return _DropBoxClient.Files.UploadAsync("/"+ fileName, body: memoryStream);

ソースに含まれているのはここですね。ストリームを使っているし、usingなので閉じることになります。

DropBoxClient.Files.UploadAsyncは名前もそうだし返値もTaskのようなので、非同期処理でしょう。
非同期処理ということは、呼び出したらすぐ返ってきます。実際にファイルをアップロードするのは後のことです。
UploadAsyncがすぐ返ってきた後、続けてそれ以降の処理をするわけですが、もうUploadFileメソッドの末尾なので同じくすぐ返ります。
おっと、返る前にusingされているMemoryStreamを破棄することが必要ですね。

ということで、実際にファイルがアップロードされる前にMemoryStreamが破棄されてしまったという状況です。

実際にアップロードが完了してからMemoryStreamの破棄をするようにする必要があります。
ではいつ実際にアップロードが完了するのかというと、UploadAsyncが返すTaskが完了状態になったらということになります。
TaskオブジェクトにContinueWithというメソッドがあって、そのTaskが完了した後に実行する処理を記述するという方法もありますが、C#では言語に組み込みでそういう処理を手続的に記述することができるようになっています。それがawaitです。
今回の場合、次のように記述できます。

// awaitを使う場合、メソッド宣言部でTaskの代わりにasync Taskとする必要があります
// public async Task UploadFile(string filePath)

using MemoryStream memoryStream = new(File.ReadAllBytes(filePath));
await _DropBoxClient.Files.UploadAsync("/"+ fileName, body: memoryStream);
return;

UploadAsyncをawaitすると、これ以降に記述されている処理は実際にはUploadAsyncの実処理が完了されてから実行されることになります。usingでの後処理にも対応しています。
また明示的にTaskを返す必要はなく、単純にreturnするだけでコンパイラがよろしく適切なTaskを返してくれます(通常のメソッドと同じく、メソッド末尾ならreturnは省略できます)。


ちなみに今回の問題に限って言えば、実はMemoryStreamはDisposeする必要のないオブジェクトなので、単にusingを削除するだけでも事足りたりはしますが…。


> (2)エラーメッセージのどこを見てどういう情報を調べれば独学でエラーを直せたか。

エラーメッセージにストリームが閉じているとあるので、using/MemoryStreamまでは推測できるでしょう。
そこにご自分で理解していないとおっしゃっているTask、非同期処理が露骨に絡んでいるのですから、まずそこを調査するのが必要だったのではないでしょうか。

> (3)内部例外2は内部例外1が原因で引き起こされているという理解でよいか。

逆ですね。
まずObjectDisposedException()がスローされ、それをキャッチして、HttpRequestExceptionをスローされています。HttpRequestExceptionの内部例外としてキャッチしたObjectDisposedExceptionが設定されています。
引用返信 編集キー/
■103423 / inTopicNo.4)  Re[2]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ 高橋 (4回)-(2024/11/07(Thu) 07:28:01)
魔界の仮面弁士さん

いつもありがとうございます。
大変勉強になります。

> DropBox.Api のことでしょうか。
> https://www.dropbox.com/developers/documentation/dotnet#install
> https://www.nuget.org/packages/Dropbox.Api/7.0.0
はい。それのことです。
DropBox.APIというのが正式名称なのですね。
ご指摘ありがとうございます。


> async Task<FileMetadata> UploadFileAsync(
> にはしないのでしょうか?
(中略)
> 非同期処理の完了を await せずに Dispose したらマズイと思いますよ。
> このコードでは、アップロードが完了する前に Dispose されてしまう可能性があるかと。

エラーを吐いたときによくわからずにAsync awaitをつけたり外したりしてデッドロックしたり、エラーがキャッチできなくなって原因がわけわからなくなったので外しました。
ただ、 Hongliangさんにも指摘いただいている通り非同期処理/同期処理を理解せずに非同期処理のAPIを使うことが無茶のようなので勉強します。
今はするべきかしないべきか分かりませんが、勉強して理解したら多分async Task<FileMetadata>にします。

> それと、波括弧の扱いが奇妙に見えます。文法上は間違いでは無いのですが、
> 使うのが「using」であるのなら、丸括弧を併用して
(中略)
> とする方が自然でしょう。
初めてusingを使い始めたときの癖で未だに波括弧を入れてしまっていますが意図したものではありませんので外しておきます。
※昔(というと雑ですが前)のC#は確か波括弧必須でしたよね?


> 非同期の場合は
(中略)
> のように、アップロードまでの一連の処理を、単一の静的メソッドに押し込める手も。
アップロードまでを静的メソッドに押し込むのは確かに便利そうですね。
ただ、この機に非同期処理/同期処理から逃げないようにします。
今裏でボタンを押したらDBからデータを出力して加工する処理が走るだけなのに画面が30分固まるとかいう阿呆なソフトウェアになっているので。


Hongliangさん

ご教示ありがとうございます。


>> (3)内部例外2は内部例外1が原因で引き起こされているという理解でよいか。
>
>逆ですね。
>まずObjectDisposedException()がスローされ、それをキャッチして、HttpRequestExceptionをスローされています。HttpRequestExceptionの内部例外としてキャッチしたObjectDisposedExceptionが設定されています。
HttpRequestExceptionのエラーをスローしてDisposeした後にMemoryStreamにアクセスしようとしていたんだと思っていたんですが逆なのですね。


>ちなみに今回の問題に限って言えば、実はMemoryStreamはDisposeする必要のないオブジェクトなので、単にusingを削除するだけでも事足りたりはしますが…。
Streamなんて典型的なDisposeの必要があるオブジェクトだと思っていましたがDisposeしないでよい状況があるのですか。
すぐにreturnして関数が終わるからでしょうか。


>> (2)エラーメッセージのどこを見てどういう情報を調べれば独学でエラーを直せたか。
>エラーメッセージにストリームが閉じているとあるので、using/MemoryStreamまでは推測できるでしょう。
>そこにご自分で理解していないとおっしゃっているTask、非同期処理が露骨に絡んでいるのですから、まずそこを調査するのが必要だったのではないでしょうか。
(3)を逆にとらえていてMemoryStream周りはスルーしていました...。
エラーメッセージは理解するのが大事ですね...。

> (3)内部例外2は内部例外1が原因で引き起こされているという理解でよいか。

逆ですね。
まずObjectDisposedException()がスローされ、それをキャッチして、HttpRequestExceptionをスローされています。HttpRequestExceptionの内部例外としてキャッチしたObjectDisposedExceptionが設定されています。



お二方回答ありがとうございます。

数日頑張ってみて動作するようになるか、数日では動作させることさえ難しいほどに根本的な理解に時間がかかりそうだと思ったら戻ってきて解決済みにさせていただきます。
引用返信 編集キー/
■103424 / inTopicNo.5)  Re[3]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ Hongliang (1305回)-(2024/11/07(Thu) 11:05:19)
> Streamなんて典型的なDisposeの必要があるオブジェクトだと思っていましたがDisposeしないでよい状況があるのですか。
> すぐにreturnして関数が終わるからでしょうか。

StreamではなくMemoryStreamの話です。
これは実体としてはただのバイト配列なので、Disposeで片付けるべき"何か"は存在していません(byte[]を使い終えた後何もしないのと同様に)。
なので必ずしもDisposeを呼ぶ必要はありません。

ただまあIDisposableをもってるものは必要なタイミングにそのDisposeの実装内容に関わらず常にDisposeするという姿勢は妥当であると思うので、「using消せばエラーなくなりました!」は良くないとは思います(自分でそういう回避策提示しておいて言うのもなんですが…)。
引用返信 編集キー/
■103425 / inTopicNo.6)  Re[1]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ kiku (444回)-(2024/11/07(Thu) 18:13:36)
No103420 (高橋 さん) に返信
> 開発環境:Microsoft Visual Studio Professional 2022 (64 ビット) - Current Version 17.8.6
> 使用言語:C#
> フレームワーク:.Net8.0
>
>
> 昨晩投稿したつもりが投稿できていなかったので再投稿します。
> もし重複していたら片方後日削除いたします。
>
> DropBoxAPIを用いてファイルをアップロードする処理を書いています。
> ただ、エラーを吐いてしまいうまくアップロードができません。
> それについて3点ご教示いただけないでしょうか。
> (1)このエラーの原因と対処法
> (2)エラーメッセージのどこを見てどういう情報を調べれば独学でエラーを直せたか。
> (3)内部例外2は内部例外1が原因で引き起こされているという理解でよいか。
>
> 【エラー内容】
> System.AggregateException
> HResult=0x80131500
> Message=One or more errors occurred. (Error while copying content to a stream.)
> (中略)
> この例外は、最初にこの呼び出し履歴
> [外部コード] でスローされました
>
> 内部例外 1:
> HttpRequestException: Error while copying content to a stream.
>
> 内部例外 2:
> ObjectDisposedException: Cannot access a closed Stream.

非同期処理の書き方や動作をきちんと理解することは必須ではありますが、
参考にするなら、下記の公式のサンプルが良いと思います。
uploadメソッドを見てください。
https://github.com/dropbox/dropbox-sdk-dotnet/blob/main/dropbox-sdk-dotnet/Examples/SimpleTest/Program.cs
引用返信 編集キー/
■103426 / inTopicNo.7)  Re[2]: DropBoxAPIでのアップロードを失敗する。
□投稿者/ 高橋 (5回)-(2024/11/11(Mon) 08:29:00)
魔界の仮面弁士さん、Hongliangさん

回答ありがとうございます。
無事想定通りの挙動で動き始めました。
AccessTokenを自動取得させようとした場合、非同期メソッドをコンストラクタに放り込まないといけないとか不都合がいろいろ出てきましたが
少なくとも今回の質問分については無事問題を解決いたしました。


Hongliangさん

言われてみればMemoryStreamは文字通りMemoryなのでDisposeがいらないと言われればその通りですね。
他のポインタがどうなのかとか、Disposeし忘れがどうなのかとか考えると怖いので多分今後もDisposeし続けますが...


kikuさん

ありがとうございます。
サンプルを見ながら作って、自分なりに添削した結果無事動作し始めました。
また、そのままサンプルのRefreshToken関連も活用させていただきました。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ