92033


提示コードの`#コンバート`関数の`///`内部の`new
String(file[i])`で以下の例外が発生してしまいます。これはなぜなのでしょうか？`Index was out of
range.`という例外が出る理由がわかりません。

セマフォを使って三つにスレッドを制限しています。  
また同じパスのものが`Run()`関数の中を通ったりと全体に的に挙動がおかしいのですが原因がわかりません。

##### 例外

[code]

    例外がスローされました: 'System.ArgumentOutOfRangeException' (System.Private.CoreLib.dll の中)
    例外がスローされました: 'System.ArgumentOutOfRangeException' (System.Private.CoreLib.dll の中)
    型 'System.ArgumentOutOfRangeException' の例外が System.Private.CoreLib.dll で発生しましたが、ユーザー コード内ではハンドルされませんでした
    Index was out of range. Must be non-negative and less than the size of the collection.
    
[/code]

##### 試したこと、確認したこと

file.Countで５個のファイルパスがあります。  
fileリスト変数の中身を確認して中のパスを確認しました、同じもはないです。  
for文も回っていますがなぜか`５ループ目`が実行されています。  
fileの変数の中身を数を確認しまししたが問題はありませんでした。  
セマフォを使った非同期処理を行っています。  
stringは参照型なのでnewしてみまいしたが同じ挙動でした。(提示コード)

[code]

    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Runtime.ConstrainedExecution;
    using System.Threading;
    using System.Windows.Forms;
    using ImageMagick;
    
    namespace HEIC_SimpleConverter
    {
        public partial class Form : System.Windows.Forms.Form
        {
            int threadNum = 3;
            int num = 0;
            string extension = ".jpeg";
            SemaphoreSlim semaphore;
            List<string> file = new List<string>();
            float per = 0;
            string folderPath = "";
            List<Task> task = new List<Task>();
    
            public Form()
            {
                InitializeComponent();
    
    
                semaphore = new SemaphoreSlim(threadNum);
            }
    
            
            /*##########################################################################
            # D&D されたとき
            ############################################################################*/
            private void Form_DragDrop(object sender, DragEventArgs e)
            {
                string[] str = (string[])e.Data.GetData(DataFormats.FileDrop, false);
                //Debug.WriteLine(str[0]);
    
                for (int i = 0; i < str.Length; i++)
                {
                    file.Add(str[i]);
                    Debug.WriteLine(str[i]);
                    //listBox.Items.Add(Path.GetFileName(str[i]));
                    listBox.Items.Add(str[i]);
                }
            }
    
    
            /*##########################################################################
            # ファイルがウインドウに乗った時
            ############################################################################*/
            private void Form_DragEnter(object sender, DragEventArgs e)
            {
                if (e.Data.GetDataPresent(DataFormats.FileDrop))
                {
                    string[] str = (string[])e.Data.GetData(DataFormats.FileDrop, false);
    
                    bool flag = false;
                    for(int i = 0; i < str.Length; i++)
                    {
                        string ext = Path.GetExtension(str[i]);
                        if ( (ext != ".HEIC") && (ext != "HEIF") )
                        {
                            flag = true;
                            break;
                        }
                    }
    
                    if(flag == true)
                    {
                        e.Effect = DragDropEffects.None;
                    }
                    else
                    {
                        e.Effect = DragDropEffects.All;
                    }
                }
                else
                {
                    e.Effect = DragDropEffects.None;
                }
    
            }
    
            /*##########################################################################
            # コンバートボタン　クリック
            ############################################################################*/
            private void button_Click(object sender, EventArgs e)
            {
                folderBrowserDialog.ShowDialog();                   //保存場所表示
                folderPath = folderBrowserDialog.SelectedPath;      //保存場所を格納
    
                Convert();      //保存ディレクトリ指定
            }
    
    
            /*##########################################################################
            # コンバート
            ############################################################################*/
            private async void Convert()
            {
                progressBar.Value = 0;
                per = 100.0f / (float)file.Count;
    
                int t = 0;
                semaphore.Release(threadNum);
    /////////////////////////////////////////////////////////////////////////////////////////
                for (int i = 0; i < file.Count; i++)
                {
                    Debug.WriteLine("file       " + file[i]);
                    task.Add(Task.Run(() => Run(new String(file[i]))));
    
                }
    //////////////////////////////////////////////////////////////////////////////////////////
                button.Enabled = false;
                await Task.WhenAll(task.ToArray());
    
             //   semaphore.Release(threadNum);
    
                button.Enabled = true;
    
                //リセット
                progressBar.Value = 0;
                file.Clear();
                listBox.Items.Clear();
            }
    
            /*##########################################################################
            # 非同期関数
            ############################################################################*/
            private void Run(string path)
            {
                
    
                string filePath = folderPath + "/" + Path.GetFileName(Path.ChangeExtension(path, extension));
                semaphore.Wait();
                ImageMagick.MagickImage img = new ImageMagick.MagickImage(path);
                img.Write(filePath);
    
                Debug.WriteLine(filePath);
    
                img.Dispose();
    
                this.Invoke(() => 
                { 
                    progressBar.Value += (int)per; 
                    //listBox.Items.Remove(path);
                });
    
    
                    
                this.Invoke(() => { num++; Debug.WriteLine(num); });
    
                semaphore.Release();
            }
    
    
    
    
    
    
            private void Form_Load(object sender, EventArgs e)
            {
    
            }
    
            
    
        }
    }
    
[/code]


CC BY-SA 4.0
2022-11-06T07:12:37.293
92031
2022-11-06T13:52:51.517
55177
question
c# winforms
非同期処理による System.ArgumentOutOfRangeException　例外の原因が知りたい。


Task.Run() の中は、別スレッドなので i を参照してはいけません。  
動作したときに、別の値に置き換わっていることがあります。

[code]

    for (int i = 0; i < file.Count; i++) {
        var s = file[i];
        task.Add(Task.Run(() => Run(s)));
    }
    
[/code]

これで良いと思います。


CC BY-SA 4.0
2022-11-06T08:16:37.580
92033
2022-11-06T08:16:37.580
50741
92031
answer


> 質問ですが確かに治りましたが、どういった理由なのかもう少し詳細に聞いてもいいですか？
[code]

    for (int i = 0; i < file.Count; i++)
    {
        Debug.WriteLine("file       " + file[i]);
        task.Add(Task.Run(() => Run(new String(file[i]))));
    }
    
[/code]

こちらですが、ラムダ式の中で、外側の変数`i`を参照しています。ラムダ式は構築された際の変数`i`の値が記憶されるのではなく、ラムダ式が実行された際に改めて変数`i`の値を読みます。

`for`ループは早々に進めるため、`i`はきっと`file.Count`に到達し`for`ループを抜け出ていることでしょう。結果、ラムダ式を実行するタイミングでは
`file[i]` は `file[file.Count]`
相当であり、ArgumentOutOfRangeExceptionが発生するのも当然と言えます。

* * *

別の記述方法として

[code]

    foreach (var f in file)
    {
        Debug.WriteLine("file       " + f);
        task.Add(Task.Run(() => Run(f)));
    }
    
[/code]

とか、デバッグ表示やタスク化不要なら

[code]

    Parallel.ForEach(file, f => Run(f));
    
[/code]

とか。


CC BY-SA 4.0
2022-11-06T13:52:51.517
92044
2022-11-06T13:52:51.517
4236
92031
answer