LoginSignup
1
0

More than 5 years have passed since last update.

Threadクラスを利用した例外処理

Last updated at Posted at 2019-03-02

Threadクラスを利用した例外処理の方法に苦戦したので、自分用としてアウトプットします。

Threadクラスとは?

スレッドとはメモリ空間を共有して同時に実行される制御の流れです。 Thread を使うことで並行プログラミングが可能になります。

プログラムは通常処理を記述した順に実行されるので、複数の処理を同時に実行することができません。

しかし、AとBという二つに処理を同時に実行した場合ってありますよね?

そんなとき使用できるのがThreadクラスです。

メインスレッドとカレントスレッド

プログラム開始時に生成されるスレッドはメインスレッド、現在実行中のスレッドはカレントスレッドと呼ばれます。

RubyではThread#mainを用いる事でメインスレッドを確認できます。

また、Thread#listでプログラム上に存在するスレッドが配列で表示されます。

joinメソッド

Thread#joinは、 スレッド self の実行が終了するまで、カレントスレッドを停止させるメソッドです。 limit を指定して、limit 秒過ぎても自身が終了しない場合、nil を返します。

要するに、AとBという二つの処理を同時に実行した際に、片方の処理が終わるまでもう片方の処理を停止させることが出来るメソッドです。

Aという処理が終わるまで、Bという処理を停止させるようなイメージ。

コード

テストの前提条件

・画像が21枚以上は保存できない
・既に19枚が保存されている
・2枚同時に投稿した際に片方をエラーにしたい

使用方法

①Threadのインスタンスを生成(t1,t2)
②それぞれのThreadに対してrescue文を使用し例外処理を行う
(どちらが先に実行されるのかわからないため)
③rescue文で定義した例外処理のeにe1を代入。
④どちらか片方が例外処理によって抜け出した際の、エラー文とクラス(ActiveRecord::RecordInvalid)が一致しているか?のテストを実施。

require 'rails_helper'

RSpec.describe UserService do

  context 'with 2 images uploading at the same time' do
    let(:user) { FactoryBot.create(:user) }
    let(:user_images_nearest_max) { FactoryBot.create_list(:user_image, 19, user: user) }
    let(:params){ ActionController::Parameters.new( user_id: user.id,
                                                    files:[{ file: File.open(Rails.root.join('spec', 'fixtures', 'sample_image_01.jpg')),
                                                    order: 2}])}

    before do
      user_images_nearest_max
    # ①
      t1 = Thread.new {
        ActiveRecord::Base.connection_pool.with_connection {
          service = described_class.new(params)
          service.run!     
        }
      }
     # ①
      t2 = Thread.new {
        ActiveRecord::Base.connection_pool.with_connection {
          service = described_class.new(params)
          service.run!
        }
      }
    end

    it 'returns invalid with the 21st image' do 
      e1 = nil
      e2 = nil

     # ②
      begin
        t1.join # JoinメソッドはThreadクラスを拡張したクラスのインスタンスから使用できるメソッド。別のスレッドの処理が終了するまで待機させたい処理がある場合に使用
      rescue => e
     # ③
        e1 = e
      end

     # ②
      begin
        t2.join
      rescue => e
     # ③
        e2 = e
      end

     # ④
      if e1.nil?
        expect(e2.class).to eq(ActiveRecord::RecordInvalid)
        expect(e2.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
        expect(user.user_images.count).to eq 20
      end

     # ④
      if e2.nil?
        expect(e1.class).to eq(ActiveRecord::RecordInvalid)
        expect(e1.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
        expect(user.user_images.count).to eq 20
      end
    end
  end
end

【参考記事】
https://docs.ruby-lang.org/ja/latest/class/Thread.html
https://qiita.com/k-penguin-sato/items/1326882c400cac8c109b

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0