I'd rather cast shadow rays instead of line of sight rays.
Let's say this is your view area (the potentially visible area)
######################
#####.............####
###................###
##..................##
#....................#
#....................#
#..........@.........#
#....................#
#....................#
##..................##
###................###
#####.............####
######################
The # blocks are not visible while the . are visible
Let's put some obstacle X:
######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXXX...........#
##..................##
###....X...........###
#####.............####
######################
You have a list of the X that are within the view area then you mark as hidden every tile that is behind each of this obstacle: when an obstacle is marked as hidden, you remove it from the list.
######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXX*...........#
##......##..........##
###....*#..........###
#####.###.........####
######################
In the example above you can see the shadow casted by the rightmost of the bottom wall and how this shadow delete the hidden obstacle from the list of the obstacle you have to check (X have to check;*checked).
If you get sort the list using some binary partiton so the cosest X are checked first you may slightly speed up your check.
You may use a sort of "Naval Battles" algorithm to check block of Xs at once (basically looking for an adiacent X that is in a direction that can make the shadow cone wider)
[EDIT]
Two rays are needed to cast correctly a shadow and, since a tile is rectangular, a lot of assumptions can be done using the available symmetries.
The ray coordinates can be computed using a simple space partitioning around the obstacle tile:

Each rectangular area constitutes a choice about what of the tile's corner should be taken as shadow cone edge.
This reasoning can be pushed further to connect multiple adjacent tiles and let them cast a single wider cone as follow.
The first step is to ensure that no obstacles are toward the observer direction, in that case the nearest obstacle is considered instead:
If the yellow tile is an obstacle that tile becomes the new red tile.
Now lets consider the upper cone edge:

The blue tiles are all possible candidate to let the shadow cone wider: if at least one of them is an obstacle the ray can be moved using the space partioning around that tile as seen before.
The green tile is a candidate only if the observer is above the orange line that follows:
The same stands for the other ray and for the other positions of the observer about the red obstacle.
The underlying idea is to cover as much area as possible for each cone casting and to shorten as fast as possible the list of obstacles to check.